{
  "openapi": "3.1.0",
  "info": {
    "title": "FactLot",
    "version": "0.4.0",
    "description": "The open fact layer for commercial real estate AI agents: deterministic CRE calculators with audit trails, canonical FactLot IDs, and cited-fact validation. Call these operations instead of doing lease, CAM, rent, loan, or return math yourself."
  },
  "servers": [
    {
      "url": "https://api.factlot.com"
    }
  ],
  "paths": {
    "/v1/calc/rent_schedule": {
      "post": {
        "operationId": "calc_rent_schedule",
        "summary": "Lease rent schedule",
        "description": "Build a base-rent schedule from commencement date, term, area, starting $/SF/yr, escalations (percent or fixed $/SF), and abated months. Lease months run from the commencement day-of-month with no calendar proration (warns when commencement is mid-month). Returns rent steps, totals, expiration date, and a calculation trace. Optional unit (kw, mw, unit, acre, key, room, space) relabels area and $/SF for data-center, ground, yard, residential, hotel, or parking leases without changing any number. Deterministic: use this instead of doing the math yourself; cite formula calc.rent_schedule@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "commencement_date": {
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                    "description": "Rent commencement date, YYYY-MM-DD. Month n starts on this day-of-month (clamped to month end)."
                  },
                  "term_months": {
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "maximum": 1200,
                    "description": "Lease term in months, e.g. 120 for ten years."
                  },
                  "area_sf": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "Rentable square feet, or the quantity leased in unit (kW, MW, units, acres, keys, rooms, spaces) when unit is set."
                  },
                  "starting_rent_psf_annual": {
                    "type": "number",
                    "minimum": 0,
                    "description": "Year-one base rent in dollars per SF per year (annual, not monthly), e.g. 42.5; per unit per year when unit is set (a $/kW/month quote × 12)."
                  },
                  "escalation": {
                    "default": {
                      "type": "none"
                    },
                    "description": "How base rent escalates. Omit for flat rent.",
                    "oneOf": [
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "none"
                          }
                        },
                        "required": [
                          "type"
                        ],
                        "description": "Flat rent for the whole term."
                      },
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "percent"
                          },
                          "rate": {
                            "type": "number",
                            "exclusiveMinimum": -1,
                            "exclusiveMaximum": 10,
                            "description": "Bump as a decimal, e.g. 0.03 for 3%. Bumps compound."
                          },
                          "every_months": {
                            "default": 12,
                            "description": "Months between bumps (12 = annual). The first bump takes effect in month every_months + 1.",
                            "type": "integer",
                            "exclusiveMinimum": 0,
                            "maximum": 9007199254740991
                          }
                        },
                        "required": [
                          "type",
                          "rate"
                        ],
                        "description": "Percentage bumps: rate_k = starting_rate × (1 + rate)^k."
                      },
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "fixed_psf"
                          },
                          "amount": {
                            "type": "number",
                            "description": "Dollars per SF per year (or per unit per year when unit is set) added at each bump, e.g. 0.50."
                          },
                          "every_months": {
                            "default": 12,
                            "description": "Months between bumps (12 = annual). The first bump takes effect in month every_months + 1.",
                            "type": "integer",
                            "exclusiveMinimum": 0,
                            "maximum": 9007199254740991
                          }
                        },
                        "required": [
                          "type",
                          "amount"
                        ],
                        "description": "Fixed $/SF bumps: rate_k = starting_rate + amount × k."
                      }
                    ]
                  },
                  "abated_months": {
                    "default": [],
                    "description": "Fully abated (free) months, 1-based from commencement (e.g. [1,2,3] for three months free up front). Each listed month's base rent is zero.",
                    "maxItems": 1200,
                    "type": "array",
                    "items": {
                      "type": "integer",
                      "exclusiveMinimum": 0,
                      "maximum": 9007199254740991
                    }
                  },
                  "round_rate_to_cents": {
                    "default": false,
                    "description": "Round each step's $/SF/yr to cents, as many lease rent tables do.",
                    "type": "boolean"
                  },
                  "include_monthly": {
                    "default": false,
                    "description": "Include a month-by-month table (can be long).",
                    "type": "boolean"
                  },
                  "unit": {
                    "description": "What area_sf and the per-SF fields measure: sf (default, square feet), kw or mw (data-center critical load), unit (apartment or other unit), acre (ground or yard lease), key or room (hotel), or space (parking). Every number is computed exactly as for square feet; the unit changes labels, steps, and warnings and adds unit and rate_unit to the result. Fields keep their _sf and _psf names: with unit kw, area_sf holds kW and starting_rent_psf_annual holds $/kW/yr. Omit for square feet.",
                    "type": "string",
                    "enum": [
                      "sf",
                      "kw",
                      "mw",
                      "unit",
                      "acre",
                      "key",
                      "room",
                      "space"
                    ]
                  }
                },
                "required": [
                  "commencement_date",
                  "term_months",
                  "area_sf",
                  "starting_rent_psf_annual"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/net_effective_rent": {
      "post": {
        "operationId": "calc_net_effective_rent",
        "summary": "Net effective rent (NER)",
        "description": "Compute effective rent, straight-line net effective rent, and discounted net effective rent ($/SF/yr) after free rent, TI allowance, leasing commissions, and other landlord concessions. Rent is monthly in advance from the commencement day (no calendar proration), discounted at annual rate ÷ 12. schedule.unit (kw, mw, unit, acre, key, room, space) reports $/kW/yr, $/acre/yr and so on instead of $/SF/yr. Deterministic: use this instead of doing the math yourself; cite formula calc.net_effective_rent@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "schedule": {
                    "type": "object",
                    "properties": {
                      "commencement_date": {
                        "type": "string",
                        "format": "date",
                        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                        "description": "Rent commencement date, YYYY-MM-DD. Month n starts on this day-of-month (clamped to month end)."
                      },
                      "term_months": {
                        "type": "integer",
                        "exclusiveMinimum": 0,
                        "maximum": 1200,
                        "description": "Lease term in months, e.g. 120 for ten years."
                      },
                      "area_sf": {
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "description": "Rentable square feet, or the quantity leased in unit (kW, MW, units, acres, keys, rooms, spaces) when unit is set."
                      },
                      "starting_rent_psf_annual": {
                        "type": "number",
                        "minimum": 0,
                        "description": "Year-one base rent in dollars per SF per year (annual, not monthly), e.g. 42.5; per unit per year when unit is set (a $/kW/month quote × 12)."
                      },
                      "escalation": {
                        "default": {
                          "type": "none"
                        },
                        "description": "How base rent escalates. Omit for flat rent.",
                        "oneOf": [
                          {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string",
                                "const": "none"
                              }
                            },
                            "required": [
                              "type"
                            ],
                            "description": "Flat rent for the whole term."
                          },
                          {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string",
                                "const": "percent"
                              },
                              "rate": {
                                "type": "number",
                                "exclusiveMinimum": -1,
                                "exclusiveMaximum": 10,
                                "description": "Bump as a decimal, e.g. 0.03 for 3%. Bumps compound."
                              },
                              "every_months": {
                                "default": 12,
                                "description": "Months between bumps (12 = annual). The first bump takes effect in month every_months + 1.",
                                "type": "integer",
                                "exclusiveMinimum": 0,
                                "maximum": 9007199254740991
                              }
                            },
                            "required": [
                              "type",
                              "rate"
                            ],
                            "description": "Percentage bumps: rate_k = starting_rate × (1 + rate)^k."
                          },
                          {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string",
                                "const": "fixed_psf"
                              },
                              "amount": {
                                "type": "number",
                                "description": "Dollars per SF per year (or per unit per year when unit is set) added at each bump, e.g. 0.50."
                              },
                              "every_months": {
                                "default": 12,
                                "description": "Months between bumps (12 = annual). The first bump takes effect in month every_months + 1.",
                                "type": "integer",
                                "exclusiveMinimum": 0,
                                "maximum": 9007199254740991
                              }
                            },
                            "required": [
                              "type",
                              "amount"
                            ],
                            "description": "Fixed $/SF bumps: rate_k = starting_rate + amount × k."
                          }
                        ]
                      },
                      "abated_months": {
                        "default": [],
                        "description": "Fully abated (free) months, 1-based from commencement (e.g. [1,2,3] for three months free up front). Each listed month's base rent is zero.",
                        "maxItems": 1200,
                        "type": "array",
                        "items": {
                          "type": "integer",
                          "exclusiveMinimum": 0,
                          "maximum": 9007199254740991
                        }
                      },
                      "round_rate_to_cents": {
                        "default": false,
                        "description": "Round each step's $/SF/yr to cents, as many lease rent tables do.",
                        "type": "boolean"
                      },
                      "include_monthly": {
                        "default": false,
                        "description": "Include a month-by-month table (can be long).",
                        "type": "boolean"
                      },
                      "unit": {
                        "description": "What area_sf and the per-SF fields measure: sf (default, square feet), kw or mw (data-center critical load), unit (apartment or other unit), acre (ground or yard lease), key or room (hotel), or space (parking). Every number is computed exactly as for square feet; the unit changes labels, steps, and warnings and adds unit and rate_unit to the result. Fields keep their _sf and _psf names: with unit kw, area_sf holds kW and starting_rent_psf_annual holds $/kW/yr. Omit for square feet.",
                        "type": "string",
                        "enum": [
                          "sf",
                          "kw",
                          "mw",
                          "unit",
                          "acre",
                          "key",
                          "room",
                          "space"
                        ]
                      }
                    },
                    "required": [
                      "commencement_date",
                      "term_months",
                      "area_sf",
                      "starting_rent_psf_annual"
                    ],
                    "description": "The lease's base-rent terms (same input as calc_rent_schedule)."
                  },
                  "ti_allowance_psf": {
                    "default": 0,
                    "description": "Landlord-funded tenant improvement allowance, USD per SF (per schedule.unit when set), paid once at commencement (not per year).",
                    "type": "number",
                    "minimum": 0
                  },
                  "leasing_commissions": {
                    "default": 0,
                    "description": "Total leasing commissions paid by landlord, USD.",
                    "type": "number",
                    "minimum": 0
                  },
                  "other_concessions": {
                    "default": 0,
                    "description": "Any other landlord concessions paid at commencement (moving allowance, buyout), USD.",
                    "type": "number",
                    "minimum": 0
                  },
                  "discount_rate_annual": {
                    "default": 0,
                    "description": "Annual discount rate as a decimal, e.g. 0.08 (monthly rate = annual ÷ 12); 0 gives the undiscounted (straight-line) NER.",
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1
                  }
                },
                "required": [
                  "schedule"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/compare_proposals": {
      "post": {
        "operationId": "calc_compare_proposals",
        "summary": "Compare lease proposals",
        "description": "Rank 2–20 lease proposals with different terms, sizes, free rent, TI, concessions, and operating-expense structures (gross, NNN, base year, expense stop, with opex growth) on one discounted $/SF/yr basis, per rentable or usable SF (load factor): effective occupancy cost (tenant perspective) or discounted net effective rent after landlord-borne opex and optional pre-commencement downtime (landlord perspective). Cash flows monthly in advance at annual rate ÷ 12; warns when the usable-SF ranking differs and when terms differ. schedule.unit (kw, mw, unit, acre, key, room, space, the same for every proposal) ranks per kW, MW, unit, acre, key, room, or space instead of per SF. Deterministic: use this instead of doing the math yourself; cite formula calc.compare_proposals@2 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "perspective": {
                    "default": "tenant",
                    "description": "Whose economics to rank on: tenant (effective occupancy cost, lower is better) or landlord (discounted net effective rent, higher is better).",
                    "type": "string",
                    "enum": [
                      "tenant",
                      "landlord"
                    ]
                  },
                  "discount_rate_annual": {
                    "default": 0.08,
                    "description": "Annual discount rate as a decimal, e.g. 0.08, applied to every proposal (monthly rate = annual ÷ 12, cash flows at the start of each month).",
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1
                  },
                  "area_basis": {
                    "default": "rentable",
                    "description": "Area the $/SF/yr metric is divided by: rentable (schedule.area_sf, the default) or usable (each proposal's usable_sf, or area_sf ÷ load_factor). Rents and expenses stay quoted per rentable SF; use usable when proposals have different load factors and the tenant needs a fixed usable area.",
                    "type": "string",
                    "enum": [
                      "rentable",
                      "usable"
                    ]
                  },
                  "proposals": {
                    "minItems": 2,
                    "maxItems": 20,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200,
                          "description": "Unique label for the proposal, e.g. building name."
                        },
                        "schedule": {
                          "type": "object",
                          "properties": {
                            "commencement_date": {
                              "type": "string",
                              "format": "date",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                              "description": "Rent commencement date, YYYY-MM-DD. Month n starts on this day-of-month (clamped to month end)."
                            },
                            "term_months": {
                              "type": "integer",
                              "exclusiveMinimum": 0,
                              "maximum": 1200,
                              "description": "Lease term in months, e.g. 120 for ten years."
                            },
                            "area_sf": {
                              "type": "number",
                              "exclusiveMinimum": 0,
                              "description": "Rentable square feet, or the quantity leased in unit (kW, MW, units, acres, keys, rooms, spaces) when unit is set."
                            },
                            "starting_rent_psf_annual": {
                              "type": "number",
                              "minimum": 0,
                              "description": "Year-one base rent in dollars per SF per year (annual, not monthly), e.g. 42.5; per unit per year when unit is set (a $/kW/month quote × 12)."
                            },
                            "escalation": {
                              "default": {
                                "type": "none"
                              },
                              "description": "How base rent escalates. Omit for flat rent.",
                              "oneOf": [
                                {
                                  "type": "object",
                                  "properties": {
                                    "type": {
                                      "type": "string",
                                      "const": "none"
                                    }
                                  },
                                  "required": [
                                    "type"
                                  ],
                                  "description": "Flat rent for the whole term."
                                },
                                {
                                  "type": "object",
                                  "properties": {
                                    "type": {
                                      "type": "string",
                                      "const": "percent"
                                    },
                                    "rate": {
                                      "type": "number",
                                      "exclusiveMinimum": -1,
                                      "exclusiveMaximum": 10,
                                      "description": "Bump as a decimal, e.g. 0.03 for 3%. Bumps compound."
                                    },
                                    "every_months": {
                                      "default": 12,
                                      "description": "Months between bumps (12 = annual). The first bump takes effect in month every_months + 1.",
                                      "type": "integer",
                                      "exclusiveMinimum": 0,
                                      "maximum": 9007199254740991
                                    }
                                  },
                                  "required": [
                                    "type",
                                    "rate"
                                  ],
                                  "description": "Percentage bumps: rate_k = starting_rate × (1 + rate)^k."
                                },
                                {
                                  "type": "object",
                                  "properties": {
                                    "type": {
                                      "type": "string",
                                      "const": "fixed_psf"
                                    },
                                    "amount": {
                                      "type": "number",
                                      "description": "Dollars per SF per year (or per unit per year when unit is set) added at each bump, e.g. 0.50."
                                    },
                                    "every_months": {
                                      "default": 12,
                                      "description": "Months between bumps (12 = annual). The first bump takes effect in month every_months + 1.",
                                      "type": "integer",
                                      "exclusiveMinimum": 0,
                                      "maximum": 9007199254740991
                                    }
                                  },
                                  "required": [
                                    "type",
                                    "amount"
                                  ],
                                  "description": "Fixed $/SF bumps: rate_k = starting_rate + amount × k."
                                }
                              ]
                            },
                            "abated_months": {
                              "default": [],
                              "description": "Fully abated (free) months, 1-based from commencement (e.g. [1,2,3] for three months free up front). Each listed month's base rent is zero.",
                              "maxItems": 1200,
                              "type": "array",
                              "items": {
                                "type": "integer",
                                "exclusiveMinimum": 0,
                                "maximum": 9007199254740991
                              }
                            },
                            "round_rate_to_cents": {
                              "default": false,
                              "description": "Round each step's $/SF/yr to cents, as many lease rent tables do.",
                              "type": "boolean"
                            },
                            "include_monthly": {
                              "default": false,
                              "description": "Include a month-by-month table (can be long).",
                              "type": "boolean"
                            },
                            "unit": {
                              "description": "What area_sf and the per-SF fields measure: sf (default, square feet), kw or mw (data-center critical load), unit (apartment or other unit), acre (ground or yard lease), key or room (hotel), or space (parking). Every number is computed exactly as for square feet; the unit changes labels, steps, and warnings and adds unit and rate_unit to the result. Fields keep their _sf and _psf names: with unit kw, area_sf holds kW and starting_rent_psf_annual holds $/kW/yr. Omit for square feet.",
                              "type": "string",
                              "enum": [
                                "sf",
                                "kw",
                                "mw",
                                "unit",
                                "acre",
                                "key",
                                "room",
                                "space"
                              ]
                            }
                          },
                          "required": [
                            "commencement_date",
                            "term_months",
                            "area_sf",
                            "starting_rent_psf_annual"
                          ],
                          "description": "Base-rent terms of this proposal (same input as calc_rent_schedule); schedule.area_sf is the rentable area. With schedule.unit (kw, mw, unit, acre, key, room, space), area_sf and every $/SF field of this proposal are per that unit; all proposals must use the same unit."
                        },
                        "usable_sf": {
                          "description": "Usable square feet of this proposal (at most schedule.area_sf). Give this or load_factor, not both; required for area_basis usable.",
                          "type": "number",
                          "exclusiveMinimum": 0
                        },
                        "load_factor": {
                          "description": "Rentable ÷ usable area, e.g. 1.15 (usable_sf = area_sf ÷ load_factor). Give this or usable_sf, not both.",
                          "type": "number",
                          "minimum": 1,
                          "maximum": 3
                        },
                        "ti_allowance_psf": {
                          "default": 0,
                          "description": "TI allowance paid by the landlord, USD per rentable SF, once at commencement (not per year).",
                          "type": "number",
                          "minimum": 0
                        },
                        "other_concessions": {
                          "default": 0,
                          "description": "Other cash concessions to the tenant at commencement (moving allowance, buyout), USD.",
                          "type": "number",
                          "minimum": 0
                        },
                        "leasing_commissions": {
                          "default": 0,
                          "description": "Leasing commissions paid by landlord at commencement, USD (landlord perspective only).",
                          "type": "number",
                          "minimum": 0
                        },
                        "operating_expenses": {
                          "description": "Operating-expense structure of this proposal: gross, nnn, base_year, or expense_stop, with a lease-year-1 estimate and growth. Tenant perspective adds what the tenant pays; landlord perspective deducts what the landlord bears. Omit to price no operating expenses (as before).",
                          "oneOf": [
                            {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string",
                                  "const": "gross"
                                },
                                "expense_psf_annual": {
                                  "default": 0,
                                  "description": "Estimated operating expenses the landlord pays, $/SF/yr in lease year 1 (used in the landlord perspective; the tenant pays none).",
                                  "type": "number",
                                  "minimum": 0
                                },
                                "growth_rate_annual": {
                                  "default": 0,
                                  "description": "Annual growth of operating expenses as a decimal, e.g. 0.03, compounding each lease year from commencement (lease year 1 is expense_psf_annual).",
                                  "type": "number",
                                  "exclusiveMinimum": -1,
                                  "exclusiveMaximum": 10
                                }
                              },
                              "required": [
                                "type"
                              ],
                              "description": "Full-service gross: the landlord pays all operating expenses; the tenant pays none beyond base rent."
                            },
                            {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string",
                                  "const": "nnn"
                                },
                                "expense_psf_annual": {
                                  "type": "number",
                                  "minimum": 0,
                                  "description": "Estimated operating expenses (CAM, taxes, insurance) for lease year 1, $/SF/yr on rentable area."
                                },
                                "growth_rate_annual": {
                                  "default": 0,
                                  "description": "Annual growth of operating expenses as a decimal, e.g. 0.03, compounding each lease year from commencement (lease year 1 is expense_psf_annual).",
                                  "type": "number",
                                  "exclusiveMinimum": -1,
                                  "exclusiveMaximum": 10
                                }
                              },
                              "required": [
                                "type",
                                "expense_psf_annual"
                              ],
                              "description": "Net (NNN): the tenant pays all operating expenses, estimated at expense_psf_annual in lease year 1."
                            },
                            {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string",
                                  "const": "base_year"
                                },
                                "expense_psf_annual": {
                                  "type": "number",
                                  "minimum": 0,
                                  "description": "Estimated operating expenses (CAM, taxes, insurance) for lease year 1, $/SF/yr on rentable area."
                                },
                                "base_year_psf_annual": {
                                  "description": "Base-year operating expenses, $/SF/yr. Defaults to expense_psf_annual (the base year is lease year 1).",
                                  "type": "number",
                                  "minimum": 0
                                },
                                "growth_rate_annual": {
                                  "default": 0,
                                  "description": "Annual growth of operating expenses as a decimal, e.g. 0.03, compounding each lease year from commencement (lease year 1 is expense_psf_annual).",
                                  "type": "number",
                                  "exclusiveMinimum": -1,
                                  "exclusiveMaximum": 10
                                }
                              },
                              "required": [
                                "type",
                                "expense_psf_annual"
                              ],
                              "description": "Gross with a base year: the tenant pays operating expenses above the base-year amount."
                            },
                            {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string",
                                  "const": "expense_stop"
                                },
                                "expense_psf_annual": {
                                  "type": "number",
                                  "minimum": 0,
                                  "description": "Estimated operating expenses (CAM, taxes, insurance) for lease year 1, $/SF/yr on rentable area."
                                },
                                "stop_psf_annual": {
                                  "type": "number",
                                  "minimum": 0,
                                  "description": "Expense stop, $/SF/yr: the tenant pays operating expenses above this fixed amount."
                                },
                                "growth_rate_annual": {
                                  "default": 0,
                                  "description": "Annual growth of operating expenses as a decimal, e.g. 0.03, compounding each lease year from commencement (lease year 1 is expense_psf_annual).",
                                  "type": "number",
                                  "exclusiveMinimum": -1,
                                  "exclusiveMaximum": 10
                                }
                              },
                              "required": [
                                "type",
                                "expense_psf_annual",
                                "stop_psf_annual"
                              ],
                              "description": "Gross with an expense stop: the tenant pays operating expenses above a fixed $/SF/yr stop."
                            }
                          ]
                        },
                        "additional_costs_psf_annual": {
                          "default": 0,
                          "description": "Other costs the tenant pays on top of base rent, flat $/SF/yr on rentable area (tenant perspective only), e.g. parking. Prefer operating_expenses for opex pass-throughs, which can grow and follow a base year or stop.",
                          "type": "number",
                          "minimum": 0
                        },
                        "tenant_upfront_costs": {
                          "default": 0,
                          "description": "Tenant's own costs at commencement not covered by allowances (TI overage, moving), USD.",
                          "type": "number",
                          "minimum": 0
                        },
                        "downtime_months": {
                          "default": 0,
                          "description": "Landlord perspective: vacant months before this proposal commences (no rent; the landlord carries lease-year-1 operating expenses if operating_expenses is given). The metric is then the level monthly amount over downtime plus term. Ignored for the tenant.",
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 120
                        }
                      },
                      "required": [
                        "name",
                        "schedule"
                      ]
                    },
                    "description": "2–20 proposals to compare."
                  }
                },
                "required": [
                  "proposals"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/multifamily_rent_roll": {
      "post": {
        "operationId": "calc_multifamily_rent_roll",
        "summary": "Multifamily rent roll",
        "description": "Analyze an apartment rent roll (up to 5,000 units with status occupied, notice, vacant, model, or down; market and lease rent per month; lease dates; concessions as weeks free, months free, or dollars): gross potential rent, vacancy, non-revenue units, loss to lease, concessions amortised straight-line over each lease term, effective rent income, physical and economic occupancy, effective rent per unit and per SF, a lease expiration schedule by month, and a per-unit-type summary. Single-lease mode (lease instead of units) returns the effective monthly rent after a weeks-free or months-free concession. Deterministic: use this instead of doing the math yourself; cite formula calc.multifamily_rent_roll@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "as_of": {
                    "description": "Rent roll date, YYYY-MM-DD. Required with units: leases that ended before it are month-to-month, and expirations are scheduled from it.",
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                  },
                  "units": {
                    "description": "Rent roll mode: every unit in the property, one row each (up to 5,000). Give units or lease, not both.",
                    "minItems": 1,
                    "maxItems": 5000,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "unit_id": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 100,
                          "description": "Unit number or ID as on the rent roll, e.g. \"101\"."
                        },
                        "unit_type": {
                          "description": "Floor plan or type, e.g. \"1x1\" or \"A2\". Groups the per-type summary; defaults to the bedroom count.",
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 100
                        },
                        "bedrooms": {
                          "description": "Bedrooms (0 = studio). Used to group units when unit_type is absent.",
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 20
                        },
                        "sf": {
                          "description": "Unit square feet. Needed for per-SF figures.",
                          "type": "number",
                          "exclusiveMinimum": 0
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "occupied",
                            "vacant",
                            "notice",
                            "model",
                            "down"
                          ],
                          "description": "occupied (leased and in place), notice (leased, the resident has given notice to vacate), vacant, model, or down (offline). Occupied and notice units are leased; model and down units are non-revenue."
                        },
                        "market_rent": {
                          "type": "number",
                          "minimum": 0,
                          "description": "Market (asking) rent per month for the unit, USD."
                        },
                        "lease_rent": {
                          "description": "Contract (lease) rent per month, USD, before concessions. Required for occupied and notice units; ignored for vacant, model, and down units.",
                          "type": "number",
                          "minimum": 0
                        },
                        "lease_start": {
                          "type": "string",
                          "format": "date",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                          "description": "Lease start date, YYYY-MM-DD."
                        },
                        "lease_end": {
                          "type": "string",
                          "format": "date",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                          "description": "Lease end date (last day), YYYY-MM-DD. A lease that ended before as_of is month-to-month."
                        },
                        "lease_term_months": {
                          "type": "number",
                          "exclusiveMinimum": 0,
                          "maximum": 120,
                          "description": "Lease term in months for amortising the concession, e.g. 12. Defaults to the term from lease_start to lease_end."
                        },
                        "concession": {
                          "oneOf": [
                            {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string",
                                  "const": "weeks_free"
                                },
                                "weeks": {
                                  "type": "number",
                                  "exclusiveMinimum": 0,
                                  "maximum": 104,
                                  "description": "Weeks of free rent, e.g. 6."
                                }
                              },
                              "required": [
                                "type",
                                "weeks"
                              ],
                              "description": "Weeks free: worth lease_rent × 12 ÷ 52 per week (a week of a year's rent)."
                            },
                            {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string",
                                  "const": "months_free"
                                },
                                "months": {
                                  "type": "number",
                                  "exclusiveMinimum": 0,
                                  "maximum": 24,
                                  "description": "Months of free rent, e.g. 1 or 1.5."
                                }
                              },
                              "required": [
                                "type",
                                "months"
                              ],
                              "description": "Months free: worth lease_rent × months."
                            },
                            {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string",
                                  "const": "amount"
                                },
                                "amount": {
                                  "type": "number",
                                  "minimum": 0,
                                  "description": "Total concession over the lease in USD, e.g. a $500 look-and-lease credit."
                                }
                              },
                              "required": [
                                "type",
                                "amount"
                              ],
                              "description": "A dollar concession for the whole lease."
                            }
                          ],
                          "description": "Concession on this lease: weeks_free, months_free, or a dollar amount. Amortised straight-line over the lease term; needs lease_term_months or lease_start and lease_end."
                        }
                      },
                      "required": [
                        "unit_id",
                        "status",
                        "market_rent"
                      ]
                    }
                  },
                  "lease": {
                    "description": "Single-lease mode: the effective monthly rent of one lease after a weeks-free, months-free, or dollar concession. Give units or lease, not both.",
                    "type": "object",
                    "properties": {
                      "lease_rent": {
                        "type": "number",
                        "minimum": 0,
                        "description": "Contract (lease) rent per month, USD, before concessions."
                      },
                      "market_rent": {
                        "description": "Market (asking) rent per month, USD, for the loss to lease. Optional.",
                        "type": "number",
                        "minimum": 0
                      },
                      "sf": {
                        "description": "Unit square feet, for effective rent per SF. Optional.",
                        "type": "number",
                        "exclusiveMinimum": 0
                      },
                      "lease_start": {
                        "type": "string",
                        "format": "date",
                        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                        "description": "Lease start date, YYYY-MM-DD."
                      },
                      "lease_end": {
                        "type": "string",
                        "format": "date",
                        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                        "description": "Lease end date (last day), YYYY-MM-DD. A lease that ended before as_of is month-to-month."
                      },
                      "lease_term_months": {
                        "description": "Lease term in months, e.g. 12. Give this, or lease_start and lease_end.",
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "maximum": 120
                      },
                      "concession": {
                        "description": "Concession: weeks_free, months_free, or a dollar amount, amortised straight-line over the term. Omit for none.",
                        "oneOf": [
                          {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string",
                                "const": "weeks_free"
                              },
                              "weeks": {
                                "type": "number",
                                "exclusiveMinimum": 0,
                                "maximum": 104,
                                "description": "Weeks of free rent, e.g. 6."
                              }
                            },
                            "required": [
                              "type",
                              "weeks"
                            ],
                            "description": "Weeks free: worth lease_rent × 12 ÷ 52 per week (a week of a year's rent)."
                          },
                          {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string",
                                "const": "months_free"
                              },
                              "months": {
                                "type": "number",
                                "exclusiveMinimum": 0,
                                "maximum": 24,
                                "description": "Months of free rent, e.g. 1 or 1.5."
                              }
                            },
                            "required": [
                              "type",
                              "months"
                            ],
                            "description": "Months free: worth lease_rent × months."
                          },
                          {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string",
                                "const": "amount"
                              },
                              "amount": {
                                "type": "number",
                                "minimum": 0,
                                "description": "Total concession over the lease in USD, e.g. a $500 look-and-lease credit."
                              }
                            },
                            "required": [
                              "type",
                              "amount"
                            ],
                            "description": "A dollar concession for the whole lease."
                          }
                        ]
                      }
                    },
                    "required": [
                      "lease_rent"
                    ]
                  },
                  "include_units": {
                    "default": false,
                    "description": "Rent roll mode: include a per-unit table with loss to lease, amortised concession, and effective rent (can be long).",
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/percentage_rent": {
      "post": {
        "operationId": "calc_percentage_rent",
        "summary": "Retail percentage rent",
        "description": "Percentage (overage) rent for a lease year: net sales after exclusions over a natural or fixed breakpoint, prorated for partial years, less amounts already paid. Deterministic: use this instead of doing the math yourself; cite formula calc.percentage_rent@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "gross_sales": {
                    "type": "number",
                    "minimum": 0,
                    "description": "Gross sales reported for the lease year (or partial year), USD."
                  },
                  "sales_exclusions": {
                    "default": 0,
                    "description": "Sales excluded by the lease (returns, sales tax, gift cards, employee sales, …), USD.",
                    "type": "number",
                    "minimum": 0
                  },
                  "rate": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "exclusiveMaximum": 1,
                    "description": "Percentage rent rate, e.g. 0.06 for 6%."
                  },
                  "base_rent_annual": {
                    "type": "number",
                    "minimum": 0,
                    "description": "Annual base rent, USD per year (a full lease year, even for a partial period); used for the natural breakpoint."
                  },
                  "breakpoint": {
                    "default": {
                      "type": "natural"
                    },
                    "description": "Breakpoint above which percentage rent applies. Default: natural.",
                    "oneOf": [
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "natural"
                          }
                        },
                        "required": [
                          "type"
                        ],
                        "description": "Natural breakpoint = annual base rent ÷ rate."
                      },
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "fixed"
                          },
                          "amount": {
                            "type": "number",
                            "minimum": 0,
                            "description": "Annual sales breakpoint stated in the lease, USD."
                          }
                        },
                        "required": [
                          "type",
                          "amount"
                        ],
                        "description": "Artificial (fixed) breakpoint stated in the lease."
                      }
                    ]
                  },
                  "period_fraction": {
                    "default": 1,
                    "description": "Fraction of a full lease year covered (e.g. 0.5 for a six-month partial year). Prorates the breakpoint.",
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "maximum": 1
                  },
                  "percentage_rent_paid": {
                    "default": 0,
                    "description": "Percentage rent already paid during the period, USD.",
                    "type": "number",
                    "minimum": 0
                  }
                },
                "required": [
                  "gross_sales",
                  "rate",
                  "base_rent_annual"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/cam_reconciliation": {
      "post": {
        "operationId": "calc_cam_reconciliation",
        "summary": "CAM / operating expense reconciliation",
        "description": "Reconcile a tenant's annual CAM charge: exclusions, occupancy gross-up of variable expenses, pro rata share, controllable-expense caps (compounding_from_base, simple_from_base, year_over_year), an admin fee (before or after the cap, inside or outside it, with per-line exclusions), base year or expense stop, partial-year occupancy (move-in/move-out prorates share, cap, and base by days), and estimates paid. Returns balance due and a full trace with order-of-operations warnings. Deterministic: use this instead of doing the math yourself; cite formula calc.cam_reconciliation@2 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "year": {
                    "type": "integer",
                    "minimum": 1900,
                    "maximum": 2200,
                    "description": "Calendar or lease year being reconciled."
                  },
                  "pro_rata_share": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "maximum": 1,
                    "description": "Tenant's pro rata share, 0 < share ≤ 1 (e.g. 12,000 / 150,000 = 0.08)."
                  },
                  "expenses": {
                    "minItems": 1,
                    "maxItems": 1000,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "category": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200,
                          "description": "Expense line as named in the operating statement, e.g. \"janitorial\"."
                        },
                        "amount": {
                          "type": "number",
                          "minimum": 0,
                          "description": "Building-level actual expense for the year, USD."
                        },
                        "controllable": {
                          "default": true,
                          "description": "Subject to the cap. Taxes, insurance, and utilities are typically uncontrollable.",
                          "type": "boolean"
                        },
                        "recoverable": {
                          "default": true,
                          "description": "Excluded expenses (capital items, landlord-only costs) are dropped entirely.",
                          "type": "boolean"
                        },
                        "variable": {
                          "default": false,
                          "description": "Varies with occupancy, so eligible for gross-up (janitorial, utilities, management fee).",
                          "type": "boolean"
                        },
                        "admin_fee_applies": {
                          "default": true,
                          "description": "Included in the admin_fee base. Set false for lines the lease excludes from the fee (often taxes, insurance, utilities, or a management fee already charged).",
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "category",
                        "amount"
                      ]
                    },
                    "description": "Building operating expense lines for the year."
                  },
                  "gross_up": {
                    "description": "Gross up variable expenses to the target occupancy when actual occupancy is lower. Omit if the lease has no gross-up.",
                    "type": "object",
                    "properties": {
                      "actual_occupancy": {
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "maximum": 1,
                        "description": "Average occupancy for the year, e.g. 0.82."
                      },
                      "target_occupancy": {
                        "default": 0.95,
                        "description": "Gross-up occupancy stated in the lease (commonly 0.95 or 1.0).",
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "maximum": 1
                      }
                    },
                    "required": [
                      "actual_occupancy"
                    ]
                  },
                  "structure": {
                    "default": {
                      "type": "net"
                    },
                    "description": "Expense structure from the lease. Default: net.",
                    "oneOf": [
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "net"
                          }
                        },
                        "required": [
                          "type"
                        ],
                        "description": "Tenant pays its full share of recoverable expenses (NNN / net)."
                      },
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "base_year"
                          },
                          "base_year_expenses": {
                            "type": "number",
                            "minimum": 0,
                            "description": "Total building recoverable expenses in the base year, already grossed up, USD."
                          }
                        },
                        "required": [
                          "type",
                          "base_year_expenses"
                        ],
                        "description": "Tenant pays its share of the excess over base-year expenses."
                      },
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "expense_stop"
                          },
                          "stop_amount": {
                            "type": "number",
                            "minimum": 0,
                            "description": "Building-level expense stop, USD per year."
                          }
                        },
                        "required": [
                          "type",
                          "stop_amount"
                        ],
                        "description": "Tenant pays its share of the excess over a fixed expense stop."
                      }
                    ]
                  },
                  "cap": {
                    "description": "Cap on the tenant's share of controllable expenses. Omit if the lease has no cap.",
                    "type": "object",
                    "properties": {
                      "method": {
                        "type": "string",
                        "enum": [
                          "compounding_from_base",
                          "simple_from_base",
                          "year_over_year"
                        ],
                        "description": "compounding_from_base: reference × (1+rate)^years (often called cumulative, compounding). simple_from_base: reference × (1 + rate × years) (cumulative, non-compounding). year_over_year: prior year's capped amount × (1+rate) (non-cumulative). Map the lease language to one of these explicitly."
                      },
                      "rate": {
                        "type": "number",
                        "exclusiveMinimum": -1,
                        "exclusiveMaximum": 10,
                        "description": "Annual cap as a decimal, e.g. 0.05."
                      },
                      "reference_amount": {
                        "type": "number",
                        "minimum": 0,
                        "description": "Tenant's share of controllable expenses in the reference year: the base year for *_from_base, or the prior year's capped amount for year_over_year, USD."
                      },
                      "years_elapsed": {
                        "description": "Years since the base year (required for `*_from_base`).",
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 9007199254740991
                      }
                    },
                    "required": [
                      "method",
                      "rate",
                      "reference_amount"
                    ]
                  },
                  "admin_fee": {
                    "description": "Administrative fee the lease adds to the tenant's share. Omit if the lease has none.",
                    "type": "object",
                    "properties": {
                      "rate": {
                        "type": "number",
                        "exclusiveMinimum": -1,
                        "exclusiveMaximum": 10,
                        "description": "Administrative or management fee as a decimal of the fee base, e.g. 0.15 for 15%. The base is the tenant's share of recoverable lines with admin_fee_applies (after gross-up)."
                      },
                      "applied": {
                        "default": "after_cap",
                        "description": "Fee on controllable lines computed on the tenant's share before the cap (before_cap) or on the capped amount (after_cap). Fees on uncontrollable lines are never capped. Irrelevant without a cap.",
                        "type": "string",
                        "enum": [
                          "before_cap",
                          "after_cap"
                        ]
                      },
                      "capped": {
                        "default": false,
                        "description": "True when the fee on controllable lines counts toward the controllable cap (controllable share plus fee may not exceed the cap limit); false adds it outside the cap.",
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "rate"
                    ]
                  },
                  "occupancy": {
                    "description": "Partial-year occupancy: the tenant's share, cap limit, and base-year or stop deduction are prorated by occupied days ÷ period days (both inclusive), so a full-year cap and base are not applied to a part year. Omit for a full year.",
                    "type": "object",
                    "properties": {
                      "move_in_date": {
                        "description": "Date the tenant's share starts within the reconciliation period, YYYY-MM-DD (inclusive). Defaults to period_start_date.",
                        "type": "string",
                        "format": "date",
                        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                      },
                      "move_out_date": {
                        "description": "Last day of the tenant's share within the period, YYYY-MM-DD (inclusive). Defaults to period_end_date.",
                        "type": "string",
                        "format": "date",
                        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                      },
                      "period_start_date": {
                        "description": "First day of the reconciliation period, YYYY-MM-DD. Defaults to January 1 of year; set it for a lease year.",
                        "type": "string",
                        "format": "date",
                        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                      },
                      "period_end_date": {
                        "description": "Last day of the reconciliation period, YYYY-MM-DD (at most 366 days after the start). Defaults to December 31 of year.",
                        "type": "string",
                        "format": "date",
                        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                      }
                    }
                  },
                  "estimates_paid": {
                    "default": 0,
                    "description": "Estimated CAM payments the tenant made during the year, USD (total for the year, not monthly).",
                    "type": "number",
                    "minimum": 0
                  }
                },
                "required": [
                  "year",
                  "pro_rata_share",
                  "expenses"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/cam_reconciliation_batch": {
      "post": {
        "operationId": "calc_cam_reconciliation_batch",
        "summary": "CAM reconciliation for every tenant (batch)",
        "description": "Reconcile CAM for a whole building in one call: the expense pool and gross-up once, then up to 500 tenants each with share, base year or expense stop, cap, admin fee, occupancy, estimates paid, and lease-specific excluded categories. Returns each tenant's statement (identical to calc_cam_reconciliation for that tenant, with its trace and warnings) and a building reconciliation: total billed against the recoverable pool, the unrecovered amount, and why (unleased or vacant share, lease exclusions, gross-up, caps, base years and stops, admin fees, zero floor, rounding). Deterministic: use this instead of doing the math yourself; cite formula calc.cam_reconciliation_batch@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "year": {
                    "type": "integer",
                    "minimum": 1900,
                    "maximum": 2200,
                    "description": "Calendar or lease year being reconciled."
                  },
                  "expenses": {
                    "minItems": 1,
                    "maxItems": 1000,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "category": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200,
                          "description": "Expense line as named in the operating statement, e.g. \"janitorial\"."
                        },
                        "amount": {
                          "type": "number",
                          "minimum": 0,
                          "description": "Building-level actual expense for the year, USD."
                        },
                        "controllable": {
                          "default": true,
                          "description": "Subject to the cap. Taxes, insurance, and utilities are typically uncontrollable.",
                          "type": "boolean"
                        },
                        "recoverable": {
                          "default": true,
                          "description": "Excluded expenses (capital items, landlord-only costs) are dropped entirely.",
                          "type": "boolean"
                        },
                        "variable": {
                          "default": false,
                          "description": "Varies with occupancy, so eligible for gross-up (janitorial, utilities, management fee).",
                          "type": "boolean"
                        },
                        "admin_fee_applies": {
                          "default": true,
                          "description": "Included in the admin_fee base. Set false for lines the lease excludes from the fee (often taxes, insurance, utilities, or a management fee already charged).",
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "category",
                        "amount"
                      ]
                    },
                    "description": "The building's operating expense pool for the year, entered once for every tenant: category, amount, controllable, recoverable, variable, admin_fee_applies."
                  },
                  "gross_up": {
                    "description": "Gross up variable expenses to the target occupancy when actual occupancy is lower. Omit if the lease has no gross-up.",
                    "type": "object",
                    "properties": {
                      "actual_occupancy": {
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "maximum": 1,
                        "description": "Average occupancy for the year, e.g. 0.82."
                      },
                      "target_occupancy": {
                        "default": 0.95,
                        "description": "Gross-up occupancy stated in the lease (commonly 0.95 or 1.0).",
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "maximum": 1
                      }
                    },
                    "required": [
                      "actual_occupancy"
                    ]
                  },
                  "tenants": {
                    "minItems": 1,
                    "maxItems": 500,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200,
                          "description": "Tenant or suite label, unique, e.g. \"Suite 200 – Acme\"."
                        },
                        "pro_rata_share": {
                          "type": "number",
                          "exclusiveMinimum": 0,
                          "maximum": 1,
                          "description": "Tenant's pro rata share, 0 < share ≤ 1 (e.g. 12,000 / 150,000 = 0.08)."
                        },
                        "structure": {
                          "default": {
                            "type": "net"
                          },
                          "description": "Expense structure from the lease. Default: net.",
                          "oneOf": [
                            {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string",
                                  "const": "net"
                                }
                              },
                              "required": [
                                "type"
                              ],
                              "description": "Tenant pays its full share of recoverable expenses (NNN / net)."
                            },
                            {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string",
                                  "const": "base_year"
                                },
                                "base_year_expenses": {
                                  "type": "number",
                                  "minimum": 0,
                                  "description": "Total building recoverable expenses in the base year, already grossed up, USD."
                                }
                              },
                              "required": [
                                "type",
                                "base_year_expenses"
                              ],
                              "description": "Tenant pays its share of the excess over base-year expenses."
                            },
                            {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string",
                                  "const": "expense_stop"
                                },
                                "stop_amount": {
                                  "type": "number",
                                  "minimum": 0,
                                  "description": "Building-level expense stop, USD per year."
                                }
                              },
                              "required": [
                                "type",
                                "stop_amount"
                              ],
                              "description": "Tenant pays its share of the excess over a fixed expense stop."
                            }
                          ]
                        },
                        "cap": {
                          "description": "Cap on the tenant's share of controllable expenses. Omit if the lease has no cap.",
                          "type": "object",
                          "properties": {
                            "method": {
                              "type": "string",
                              "enum": [
                                "compounding_from_base",
                                "simple_from_base",
                                "year_over_year"
                              ],
                              "description": "compounding_from_base: reference × (1+rate)^years (often called cumulative, compounding). simple_from_base: reference × (1 + rate × years) (cumulative, non-compounding). year_over_year: prior year's capped amount × (1+rate) (non-cumulative). Map the lease language to one of these explicitly."
                            },
                            "rate": {
                              "type": "number",
                              "exclusiveMinimum": -1,
                              "exclusiveMaximum": 10,
                              "description": "Annual cap as a decimal, e.g. 0.05."
                            },
                            "reference_amount": {
                              "type": "number",
                              "minimum": 0,
                              "description": "Tenant's share of controllable expenses in the reference year: the base year for *_from_base, or the prior year's capped amount for year_over_year, USD."
                            },
                            "years_elapsed": {
                              "description": "Years since the base year (required for `*_from_base`).",
                              "type": "integer",
                              "minimum": 0,
                              "maximum": 9007199254740991
                            }
                          },
                          "required": [
                            "method",
                            "rate",
                            "reference_amount"
                          ]
                        },
                        "admin_fee": {
                          "description": "Administrative fee the lease adds to the tenant's share. Omit if the lease has none.",
                          "type": "object",
                          "properties": {
                            "rate": {
                              "type": "number",
                              "exclusiveMinimum": -1,
                              "exclusiveMaximum": 10,
                              "description": "Administrative or management fee as a decimal of the fee base, e.g. 0.15 for 15%. The base is the tenant's share of recoverable lines with admin_fee_applies (after gross-up)."
                            },
                            "applied": {
                              "default": "after_cap",
                              "description": "Fee on controllable lines computed on the tenant's share before the cap (before_cap) or on the capped amount (after_cap). Fees on uncontrollable lines are never capped. Irrelevant without a cap.",
                              "type": "string",
                              "enum": [
                                "before_cap",
                                "after_cap"
                              ]
                            },
                            "capped": {
                              "default": false,
                              "description": "True when the fee on controllable lines counts toward the controllable cap (controllable share plus fee may not exceed the cap limit); false adds it outside the cap.",
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "rate"
                          ]
                        },
                        "occupancy": {
                          "description": "Partial-year occupancy: the tenant's share, cap limit, and base-year or stop deduction are prorated by occupied days ÷ period days (both inclusive), so a full-year cap and base are not applied to a part year. Omit for a full year.",
                          "type": "object",
                          "properties": {
                            "move_in_date": {
                              "description": "Date the tenant's share starts within the reconciliation period, YYYY-MM-DD (inclusive). Defaults to period_start_date.",
                              "type": "string",
                              "format": "date",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                            },
                            "move_out_date": {
                              "description": "Last day of the tenant's share within the period, YYYY-MM-DD (inclusive). Defaults to period_end_date.",
                              "type": "string",
                              "format": "date",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                            },
                            "period_start_date": {
                              "description": "First day of the reconciliation period, YYYY-MM-DD. Defaults to January 1 of year; set it for a lease year.",
                              "type": "string",
                              "format": "date",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                            },
                            "period_end_date": {
                              "description": "Last day of the reconciliation period, YYYY-MM-DD (at most 366 days after the start). Defaults to December 31 of year.",
                              "type": "string",
                              "format": "date",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                            }
                          }
                        },
                        "estimates_paid": {
                          "default": 0,
                          "description": "Estimated CAM payments the tenant made during the year, USD (total for the year, not monthly).",
                          "type": "number",
                          "minimum": 0
                        },
                        "excluded_categories": {
                          "default": [],
                          "description": "Expense categories this tenant's lease excludes, treated as recoverable: false for this tenant only. Each must match a category in expenses.",
                          "maxItems": 1000,
                          "type": "array",
                          "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          }
                        },
                        "admin_fee_excluded_categories": {
                          "default": [],
                          "description": "Expense categories this tenant's lease keeps out of the admin fee base, treated as admin_fee_applies: false for this tenant only. Each must match a category in expenses.",
                          "maxItems": 1000,
                          "type": "array",
                          "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          }
                        }
                      },
                      "required": [
                        "name",
                        "pro_rata_share"
                      ]
                    },
                    "description": "Every tenant to reconcile (up to 500): each one's share, structure (base year or stop), cap, admin fee, occupancy, estimates paid, and lease-specific exclusions. Each statement equals calc_cam_reconciliation for that tenant."
                  }
                },
                "required": [
                  "year",
                  "expenses",
                  "tenants"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/capex_recovery": {
      "post": {
        "operationId": "calc_capex_recovery",
        "summary": "Capital expenditure recovery",
        "description": "Recoverable capital expenditure under a lease clause: amortize a capital cost over its useful life or the lease-specified period, straight-line (declining interest) or level payment, with interest on the unamortized balance or none; optionally cap each year's recovery at demonstrated cost savings (with or without carry-forward); and allocate each year to tenants by pro rata share. Every assumption is echoed with an optional lease clause citation. Deterministic: use this instead of doing the math yourself; cite formula calc.capex_recovery@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cost": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "Capital cost of the improvement to recover, USD (building level)."
                  },
                  "amortization_years": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "description": "Years over which the cost is recovered: the useful life, or the amortization period the lease specifies."
                  },
                  "period_basis": {
                    "default": "useful_life",
                    "description": "Where amortization_years comes from: useful_life (e.g. under GAAP or tax guidance) or lease_specified (a period the lease states). Labels only.",
                    "type": "string",
                    "enum": [
                      "useful_life",
                      "lease_specified"
                    ]
                  },
                  "method": {
                    "default": "straight_line",
                    "description": "straight_line: equal principal (cost ÷ years) plus interest on the declining unamortized balance, so recovery falls each year. level_payment: a level annual payment like a mortgage, cost × r ÷ (1 − (1 + r)^−years), split into interest and principal. Identical when interest_rate_annual is 0.",
                    "type": "string",
                    "enum": [
                      "straight_line",
                      "level_payment"
                    ]
                  },
                  "interest_rate_annual": {
                    "default": 0,
                    "description": "Annual interest on the unamortized balance as a decimal, e.g. 0.08; 0 (default) for none. Charged on the balance at the start of each year.",
                    "type": "number",
                    "exclusiveMinimum": -1,
                    "exclusiveMaximum": 10
                  },
                  "first_year": {
                    "description": "Calendar or lease year of the first recovery year, e.g. 2026; labels the schedule rows. Omit to number years 1, 2, 3.",
                    "type": "integer",
                    "minimum": 1900,
                    "maximum": 2200
                  },
                  "cost_savings_cap": {
                    "description": "Cost-savings limit: recovery in each year may not exceed the operating-cost savings the improvement demonstrably produces. Omit if the lease has none.",
                    "type": "object",
                    "properties": {
                      "annual_savings": {
                        "description": "Demonstrated operating-cost savings per year, USD, the same every year. Give this or savings_by_year.",
                        "type": "number",
                        "minimum": 0
                      },
                      "savings_by_year": {
                        "description": "Demonstrated savings for each recovery year, USD, one per year of amortization_years. Give this or annual_savings.",
                        "minItems": 1,
                        "maxItems": 100,
                        "type": "array",
                        "items": {
                          "type": "number",
                          "minimum": 0
                        }
                      },
                      "carry_forward": {
                        "default": false,
                        "description": "True if amortization disallowed by the cap in one year may be recovered in a later year of the period (still within that year's savings); false (default) if it is lost.",
                        "type": "boolean"
                      }
                    }
                  },
                  "tenants": {
                    "description": "Tenants to allocate the annual recoverable amount to by pro rata share. Shares may sum to less than 1 (the landlord bears the rest), not more. Omit for the building-level schedule only.",
                    "minItems": 1,
                    "maxItems": 1000,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200,
                          "description": "Tenant or suite label, unique."
                        },
                        "pro_rata_share": {
                          "type": "number",
                          "exclusiveMinimum": 0,
                          "maximum": 1,
                          "description": "Tenant's pro rata share of the recoverable amount, 0 < share ≤ 1."
                        }
                      },
                      "required": [
                        "name",
                        "pro_rata_share"
                      ]
                    }
                  },
                  "clauses": {
                    "description": "Lease clause behind each assumption, free text, echoed in the result's assumptions so a reviewer can check each against the lease.",
                    "type": "object",
                    "properties": {
                      "cost": {
                        "description": "Free-text lease reference for this assumption, e.g. \"Lease §6.2(b)\" or \"First Amendment ¶4\". Echoed with the assumption.",
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 500
                      },
                      "amortization_period": {
                        "description": "Free-text lease reference for this assumption, e.g. \"Lease §6.2(b)\" or \"First Amendment ¶4\". Echoed with the assumption.",
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 500
                      },
                      "interest": {
                        "description": "Free-text lease reference for this assumption, e.g. \"Lease §6.2(b)\" or \"First Amendment ¶4\". Echoed with the assumption.",
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 500
                      },
                      "method": {
                        "description": "Free-text lease reference for this assumption, e.g. \"Lease §6.2(b)\" or \"First Amendment ¶4\". Echoed with the assumption.",
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 500
                      },
                      "cost_savings_cap": {
                        "description": "Free-text lease reference for this assumption, e.g. \"Lease §6.2(b)\" or \"First Amendment ¶4\". Echoed with the assumption.",
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 500
                      },
                      "pro_rata_share": {
                        "description": "Free-text lease reference for this assumption, e.g. \"Lease §6.2(b)\" or \"First Amendment ¶4\". Echoed with the assumption.",
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 500
                      }
                    }
                  }
                },
                "required": [
                  "cost",
                  "amortization_years"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/debt_sizing": {
      "post": {
        "operationId": "calc_debt_sizing",
        "summary": "Loan sizing (LTV / DSCR / debt yield)",
        "description": "Size the maximum loan under any combination of max LTV, min DSCR, and min debt yield for a given NOI, rate, and amortization. Reports the binding constraint, debt service, and resulting metrics; optionally a stress table over NOI, rate, and value shocks (max loan, binding constraint, LTV, DSCR, debt yield per scenario) and the refinance shortfall against an existing loan balance. Deterministic: use this instead of doing the math yourself; cite formula calc.debt_sizing@3 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "noi": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "Underwritten net operating income, USD/yr."
                  },
                  "value": {
                    "description": "Appraised or underwritten value, USD. Required when max_ltv is set.",
                    "type": "number",
                    "exclusiveMinimum": 0
                  },
                  "rate_annual": {
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1,
                    "description": "Annual interest rate, e.g. 0.065."
                  },
                  "amortization_years": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 50,
                    "description": "Amortization in years; 0 means interest-only."
                  },
                  "max_ltv": {
                    "description": "Maximum loan-to-value, e.g. 0.65.",
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "maximum": 1
                  },
                  "min_dscr": {
                    "description": "Minimum debt service coverage ratio, e.g. 1.25.",
                    "type": "number",
                    "exclusiveMinimum": 0
                  },
                  "min_debt_yield": {
                    "description": "Minimum debt yield (NOI ÷ loan), e.g. 0.09.",
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "exclusiveMaximum": 1
                  },
                  "round_down_to": {
                    "default": 1,
                    "description": "Round the final loan amount down to this increment (lenders often use 1,000 or 10,000).",
                    "type": "number",
                    "exclusiveMinimum": 0
                  },
                  "existing_loan_balance": {
                    "description": "Balance of the loan being refinanced, USD. Adds a refinance result (shortfall or excess proceeds against the max loan) and a shortfall per stress scenario.",
                    "type": "number",
                    "exclusiveMinimum": 0
                  },
                  "stress": {
                    "description": "Stress table: every combination of the NOI, rate, and value shocks (up to 1,000 scenarios), each sized under the same constraints and rounding. The base result is unchanged.",
                    "type": "object",
                    "properties": {
                      "noi_shocks": {
                        "description": "Relative NOI changes as decimals, e.g. [-0.05, -0.1, -0.2] for NOI down 5%, 10%, 20%. Omit for no NOI shock.",
                        "minItems": 1,
                        "maxItems": 10,
                        "type": "array",
                        "items": {
                          "type": "number",
                          "exclusiveMinimum": -1,
                          "exclusiveMaximum": 10
                        }
                      },
                      "rate_shocks": {
                        "description": "Absolute changes to rate_annual as decimals, e.g. [0.005, 0.01] for +50 and +100 bp. The stressed rate must stay at or above 0 and below 1. Omit for no rate shock.",
                        "minItems": 1,
                        "maxItems": 10,
                        "type": "array",
                        "items": {
                          "type": "number",
                          "exclusiveMinimum": -1,
                          "exclusiveMaximum": 1
                        }
                      },
                      "value_shocks": {
                        "description": "Relative value changes as decimals, e.g. [-0.1, -0.2]; requires value. Omit for no value shock.",
                        "minItems": 1,
                        "maxItems": 10,
                        "type": "array",
                        "items": {
                          "type": "number",
                          "exclusiveMinimum": -1,
                          "exclusiveMaximum": 10
                        }
                      },
                      "value_moves_with_noi": {
                        "default": false,
                        "description": "Also scale value by each NOI shock (value × (1 + noi_shock), a constant cap rate), as a lender repricing value on stressed NOI would.",
                        "type": "boolean"
                      }
                    }
                  }
                },
                "required": [
                  "noi",
                  "rate_annual",
                  "amortization_years"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/lease_remeasurement_asc842": {
      "post": {
        "operationId": "calc_lease_remeasurement_asc842",
        "summary": "Lease remeasurement (ASC 842, lessee)",
        "description": "Remeasure a lessee's lease liability and right-of-use asset under ASC 842 after a modification, lease-term reassessment, contingency resolution, or residual value guarantee change: new liability (PV of remaining payments), liability and ROU adjustments with any gain (ASC 842-20-35-4), and the per-period amortization table (operating: straight-line single lease cost; finance: interest plus amortization) in cents that foots: every row rolls forward exactly, totals are the row sums, and the final period trues up liability and ROU asset to zero. Warns where judgment is needed. Deterministic: use this instead of doing the math yourself; cite formula calc.lease_remeasurement_asc842@2 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "classification": {
                    "type": "string",
                    "enum": [
                      "operating",
                      "finance"
                    ],
                    "description": "Lessee classification (ASC 842-10-25-2): operating (single straight-line lease cost) or finance (interest on the liability plus straight-line ROU amortization)."
                  },
                  "payments": {
                    "minItems": 1,
                    "maxItems": 1200,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "period": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 1200,
                          "description": "Period index counted from the remeasurement date (1 = first remaining period)."
                        },
                        "amount": {
                          "type": "number",
                          "minimum": 0,
                          "description": "Lease payment due in that period after the event, USD for the period (a monthly amount when periods_per_year is 12, not annualized): fixed and in-substance fixed payments, less incentives receivable (ASC 842-10-30-5)."
                        }
                      },
                      "required": [
                        "period",
                        "amount"
                      ]
                    },
                    "description": "Remaining lease payments after the event, in ascending period order. Periods not listed carry no payment (rent-free)."
                  },
                  "remaining_periods": {
                    "description": "Remaining lease term in periods after the event. Defaults to the last period index in payments; set it higher when the final periods are rent-free.",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1200
                  },
                  "payment_timing": {
                    "default": "end",
                    "description": "Whether each payment falls at the beginning (in advance) or end (in arrears) of its period.",
                    "type": "string",
                    "enum": [
                      "begin",
                      "end"
                    ]
                  },
                  "discount_rate_annual": {
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1,
                    "description": "Annual discount rate applied to the remaining payments, e.g. 0.06. Nominal: the periodic rate is this ÷ periods_per_year."
                  },
                  "periods_per_year": {
                    "default": 12,
                    "description": "Payment periods per year (12 for monthly, 4 for quarterly).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 365
                  },
                  "discount_rate_updated": {
                    "type": "boolean",
                    "description": "True when discount_rate_annual was determined at the remeasurement date (ASC 842-10-35-5: required for modifications and lease-term or purchase-option reassessments); false when it is the rate from commencement (kept for contingency resolutions and residual value guarantee changes)."
                  },
                  "liability_before": {
                    "type": "number",
                    "minimum": 0,
                    "description": "Carrying amount of the lease liability immediately before the remeasurement, USD."
                  },
                  "rou_asset_before": {
                    "type": "number",
                    "minimum": 0,
                    "description": "Carrying amount of the right-of-use asset immediately before the remeasurement, USD (after any impairment)."
                  },
                  "event": {
                    "type": "string",
                    "enum": [
                      "modification_extend_term",
                      "modification_shorten_term",
                      "modification_change_in_payments",
                      "reassessment_of_term_or_option",
                      "contingency_resolved",
                      "residual_value_guarantee_change",
                      "index_or_rate_change"
                    ],
                    "description": "What triggered the remeasurement: modification_extend_term / modification_shorten_term / modification_change_in_payments (a modification not accounted for as a separate contract, ASC 842-10-25-8 and 25-11), reassessment_of_term_or_option (ASC 842-10-35-1 and 35-4(c)), contingency_resolved (variable payments became fixed, ASC 842-10-35-4(b)), residual_value_guarantee_change (ASC 842-10-35-4(c)(3)), or index_or_rate_change (ASC 842-10-35-4: not a remeasurement trigger by itself; computed as if remeasured for another reason)."
                  }
                },
                "required": [
                  "classification",
                  "payments",
                  "discount_rate_annual",
                  "discount_rate_updated",
                  "liability_before",
                  "rou_asset_before",
                  "event"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/prepayment_and_defeasance": {
      "post": {
        "operationId": "calc_prepayment_and_defeasance",
        "summary": "Loan prepayment premium and defeasance",
        "description": "Prepayment premium, total payoff, and cost as a share of balance for a fixed-rate amortizing or interest-only loan under yield maintenance (PV of remaining payments or rate differential, with a minimum and an explicit discounting convention), a step-down schedule, a flat percentage, or defeasance (cost of the securities portfolio that funds the remaining payments plus stated costs). Deterministic: use this instead of doing the math yourself; cite formula calc.prepayment_and_defeasance@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "balance": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "Outstanding principal balance at the payoff date, USD."
                  },
                  "note_rate_annual": {
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1,
                    "description": "Fixed annual note rate, e.g. 0.055."
                  },
                  "remaining_term_months": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 600,
                    "description": "Months from the payoff date to the loan's maturity date (the balloon is due with the final payment)."
                  },
                  "amortization_months": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 720,
                    "description": "Remaining amortization basis in months at the payoff date (e.g. 300 for a 30-year schedule with 5 years elapsed); 0 for interest-only. Must be at least remaining_term_months."
                  },
                  "accrued_interest": {
                    "default": 0,
                    "description": "Accrued but unpaid interest to the payoff date, USD; added to the total payoff.",
                    "type": "number",
                    "minimum": 0
                  },
                  "prepayment": {
                    "oneOf": [
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "yield_maintenance"
                          },
                          "treasury_rate_annual": {
                            "type": "number",
                            "minimum": 0,
                            "exclusiveMaximum": 1,
                            "description": "Yield on the reference Treasury (usually the one maturing closest to the loan's maturity), e.g. 0.041."
                          },
                          "spread_annual": {
                            "default": 0,
                            "description": "Spread added to the Treasury yield for discounting, e.g. 0.005 when the note says 'Treasury plus 50 basis points'. Default 0 (Treasury flat).",
                            "type": "number",
                            "minimum": 0,
                            "exclusiveMaximum": 1
                          },
                          "minimum_pct": {
                            "default": 0.01,
                            "description": "Minimum premium as a fraction of the balance, e.g. 0.01 for a 1% floor (default). 0 for no floor.",
                            "type": "number",
                            "minimum": 0,
                            "exclusiveMaximum": 1
                          },
                          "basis": {
                            "default": "pv_of_payments",
                            "description": "pv_of_payments (default): premium = PV of the remaining scheduled payments at the discount rate − balance. rate_differential: premium = PV of (note rate − discount rate) ÷ 12 × scheduled balance for each remaining month.",
                            "type": "string",
                            "enum": [
                              "pv_of_payments",
                              "rate_differential"
                            ]
                          },
                          "discount_convention": {
                            "default": "monthly",
                            "description": "Compounding convention for the discount rate. monthly (default): monthly rate = annual ÷ 12. semiannual: annual is a bond-equivalent yield, monthly rate = (1 + annual ÷ 2)^(1/6) − 1. Lenders differ; read the note.",
                            "type": "string",
                            "enum": [
                              "monthly",
                              "semiannual"
                            ]
                          }
                        },
                        "required": [
                          "type",
                          "treasury_rate_annual"
                        ],
                        "description": "Yield maintenance."
                      },
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "step_down"
                          },
                          "loan_month": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 9007199254740991,
                            "description": "Loan month (1 = first month after origination) in which the payoff occurs."
                          },
                          "schedule": {
                            "minItems": 1,
                            "maxItems": 1200,
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "through_month": {
                                  "type": "integer",
                                  "minimum": 1,
                                  "maximum": 9007199254740991,
                                  "description": "Last loan month in which this percentage applies."
                                },
                                "pct": {
                                  "type": "number",
                                  "minimum": 0,
                                  "exclusiveMaximum": 1,
                                  "description": "Premium as a fraction of the balance, e.g. 0.05 for 5%."
                                }
                              },
                              "required": [
                                "through_month",
                                "pct"
                              ]
                            },
                            "description": "Step-down schedule in ascending through_month order, e.g. 5-4-3-2-1. After the last through_month the loan is open (0%)."
                          }
                        },
                        "required": [
                          "type",
                          "loan_month",
                          "schedule"
                        ],
                        "description": "Step-down (declining percentage) premium."
                      },
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "fixed_pct"
                          },
                          "pct": {
                            "type": "number",
                            "minimum": 0,
                            "exclusiveMaximum": 1,
                            "description": "Premium as a fraction of the balance, e.g. 0.02."
                          }
                        },
                        "required": [
                          "type",
                          "pct"
                        ],
                        "description": "Flat percentage premium."
                      },
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "defeasance"
                          },
                          "replacement_yield": {
                            "anyOf": [
                              {
                                "type": "number",
                                "minimum": 0,
                                "exclusiveMaximum": 1,
                                "description": "One flat annual yield for all replacement securities."
                              },
                              {
                                "minItems": 1,
                                "maxItems": 1200,
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "months": {
                                      "type": "integer",
                                      "minimum": 1,
                                      "maximum": 9007199254740991,
                                      "description": "Months from the payoff date up to and including which this yield applies; points in ascending order."
                                    },
                                    "rate": {
                                      "type": "number",
                                      "minimum": 0,
                                      "exclusiveMaximum": 1,
                                      "description": "Annual yield on replacement securities maturing at or before that month, e.g. 0.042."
                                    }
                                  },
                                  "required": [
                                    "months",
                                    "rate"
                                  ]
                                },
                                "description": "Yield curve as a step function: each payment uses the first point whose months ≥ its month; beyond the last point, the last rate."
                              }
                            ],
                            "description": "Yield on the government securities bought to fund the remaining payments: a flat annual rate, or a list of {months, rate} points."
                          },
                          "costs": {
                            "default": 0,
                            "description": "Stated transaction costs, USD: defeasance consultant, legal, accountant, rating agency, servicer and custodian fees.",
                            "type": "number",
                            "minimum": 0
                          },
                          "discount_convention": {
                            "default": "monthly",
                            "description": "Compounding convention for the discount rate. monthly (default): monthly rate = annual ÷ 12. semiannual: annual is a bond-equivalent yield, monthly rate = (1 + annual ÷ 2)^(1/6) − 1. Lenders differ; read the note.",
                            "type": "string",
                            "enum": [
                              "monthly",
                              "semiannual"
                            ]
                          }
                        },
                        "required": [
                          "type",
                          "replacement_yield"
                        ],
                        "description": "Defeasance: buy securities that replicate the remaining payments."
                      }
                    ],
                    "description": "Prepayment provision from the note."
                  }
                },
                "required": [
                  "balance",
                  "note_rate_annual",
                  "remaining_term_months",
                  "amortization_months",
                  "prepayment"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/walt": {
      "post": {
        "operationId": "calc_walt",
        "summary": "Weighted average lease term (WALT)",
        "description": "Weighted average remaining lease term in years as of a date (actual days ÷ 365.25), weighted by each lease's area_sf (weight_by \"area\") or annual_rent (weight_by \"rent\"). Deterministic: use this instead of doing the math yourself; cite formula calc.walt@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "as_of": {
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                    "description": "Date to measure remaining term from, YYYY-MM-DD."
                  },
                  "weight_by": {
                    "default": "area",
                    "description": "How each lease is weighted: \"area\" uses its area_sf, \"rent\" its annual_rent (that field is then required on every lease). Default \"area\".",
                    "type": "string",
                    "enum": [
                      "area",
                      "rent"
                    ]
                  },
                  "leases": {
                    "minItems": 1,
                    "maxItems": 10000,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "description": "Your identifier for the lease (ideally a FactLot lease ID).",
                          "type": "string",
                          "maxLength": 200
                        },
                        "expiration_date": {
                          "type": "string",
                          "format": "date",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                          "description": "Lease expiration date, YYYY-MM-DD."
                        },
                        "area_sf": {
                          "description": "Leased square feet (required when weight_by is area).",
                          "type": "number",
                          "exclusiveMinimum": 0
                        },
                        "annual_rent": {
                          "description": "Current annual rent, USD per year (required when weight_by is rent).",
                          "type": "number",
                          "exclusiveMinimum": 0
                        }
                      },
                      "required": [
                        "expiration_date"
                      ]
                    },
                    "description": "Leases in the portfolio or building."
                  }
                },
                "required": [
                  "as_of",
                  "leases"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/npv": {
      "post": {
        "operationId": "calc_npv",
        "summary": "Net present value",
        "description": "NPV of periodic cash flows (t = 0, 1, 2, ...; the t = 0 flow is not discounted) at a per-period discount rate given as a decimal. Deterministic: use this instead of doing the math yourself; cite formula calc.npv@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cash_flows": {
                    "minItems": 2,
                    "maxItems": 10000,
                    "type": "array",
                    "items": {
                      "type": "number"
                    },
                    "description": "Periodic cash flows at t = 0, 1, 2, …; negative for outflows (e.g. purchase price at t = 0). At most 10000."
                  },
                  "rate": {
                    "type": "number",
                    "exclusiveMinimum": -1,
                    "description": "Discount rate per period as a decimal, e.g. 0.08 for 8%. Match the cash-flow period: for monthly flows pass the monthly rate (e.g. 0.06 ÷ 12 = 0.005)."
                  }
                },
                "required": [
                  "cash_flows",
                  "rate"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/irr": {
      "post": {
        "operationId": "calc_irr",
        "summary": "Internal rate of return",
        "description": "Periodic and annualized IRR of periodic cash flows, as decimals (0.085 = 8.5%), solved deterministically by scanning a 1-point grid of per-period rates and bisecting each sign change; reports every root it finds when cash flows have multiple IRRs (two roots within one grid step can be missed). Errors when the flows do not change sign or no root lies between -99.99% and 1000% per period. Deterministic: use this instead of doing the math yourself; cite formula calc.irr@2 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cash_flows": {
                    "minItems": 2,
                    "maxItems": 1200,
                    "type": "array",
                    "items": {
                      "type": "number"
                    },
                    "description": "Periodic cash flows at t = 0, 1, 2, …; negative for outflows (e.g. purchase price at t = 0). At most 1200."
                  },
                  "periods_per_year": {
                    "default": 1,
                    "description": "Cash-flow periods per year, used only to annualize the result: 1 for annual flows, 4 for quarterly, 12 for monthly.",
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "maximum": 9007199254740991
                  }
                },
                "required": [
                  "cash_flows"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/lease_vs_own": {
      "post": {
        "operationId": "calc_lease_vs_own",
        "summary": "Lease vs. own",
        "description": "Compare leasing with buying over a fixed horizon: NPV of each case's annual cash costs (rent, free rent, tenant expenses, and move-in costs versus equity, debt service, owner expenses, reserves, and a sale at the horizon by stated value or exit cap rate), optionally after tax, with the year-by-year tables and the break-even residual value at which owning equals leasing. Deterministic: use this instead of doing the math yourself; cite formula calc.lease_vs_own@2 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "horizon_years": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 50,
                    "description": "Analysis horizon in years. Both cases are compared over this period; the own case sells at the end of it."
                  },
                  "discount_rate_annual": {
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1,
                    "description": "Annual discount rate as a decimal, e.g. 0.08, applied to both cases' year-end cash flows (year 0 = closing / move-in)."
                  },
                  "lease": {
                    "type": "object",
                    "properties": {
                      "rent": {
                        "oneOf": [
                          {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string",
                                "const": "schedule"
                              },
                              "annual_rent": {
                                "minItems": 1,
                                "maxItems": 50,
                                "type": "array",
                                "items": {
                                  "type": "number",
                                  "minimum": 0
                                },
                                "description": "Rent due in each year of the horizon, USD, year 1 first. Must have exactly horizon_years entries."
                              }
                            },
                            "required": [
                              "type",
                              "annual_rent"
                            ],
                            "description": "Explicit rent per year."
                          },
                          {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string",
                                "const": "escalating"
                              },
                              "base_rent_annual": {
                                "type": "number",
                                "minimum": 0,
                                "description": "Year-1 rent, USD per year."
                              },
                              "escalation_rate_annual": {
                                "default": 0,
                                "description": "Compounding annual bump, e.g. 0.03: rent_k = base × (1 + e)^(k − 1).",
                                "type": "number",
                                "minimum": 0,
                                "exclusiveMaximum": 1
                              }
                            },
                            "required": [
                              "type",
                              "base_rent_annual"
                            ],
                            "description": "Base rent with a compounding annual escalation."
                          }
                        ],
                        "description": "Annual rent, as an explicit schedule or base rent with escalation."
                      },
                      "free_rent_months": {
                        "default": 0,
                        "description": "Rent-free months at the start of the lease, abated from year 1 (and year 2 if more than 12). Must not exceed the horizon.",
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 9007199254740991
                      },
                      "operating_expenses_annual": {
                        "default": 0,
                        "description": "Year-1 operating expenses borne by the tenant on top of rent (NNN charges, utilities), USD per year.",
                        "type": "number",
                        "minimum": 0
                      },
                      "expense_growth_rate_annual": {
                        "default": 0,
                        "description": "Annual growth applied to expenses after year 1, e.g. 0.03 (year k = year-1 amount × (1 + g)^(k − 1)).",
                        "type": "number",
                        "minimum": 0,
                        "exclusiveMaximum": 1
                      },
                      "upfront_costs": {
                        "default": 0,
                        "description": "Tenant's one-time costs at move-in not covered by allowances: TI overage, moving, security deposit treated as cost, USD.",
                        "type": "number",
                        "minimum": 0
                      }
                    },
                    "required": [
                      "rent"
                    ],
                    "description": "The lease alternative."
                  },
                  "own": {
                    "type": "object",
                    "properties": {
                      "purchase_price": {
                        "type": "number",
                        "minimum": 0,
                        "description": "Purchase price, USD."
                      },
                      "closing_costs": {
                        "default": 0,
                        "description": "Acquisition closing costs, USD (added to the purchase price for cash and for depreciable basis).",
                        "type": "number",
                        "minimum": 0
                      },
                      "loan": {
                        "description": "Acquisition financing. Omit for an all-cash purchase.",
                        "type": "object",
                        "properties": {
                          "amount": {
                            "type": "number",
                            "minimum": 0,
                            "description": "Loan principal at closing, USD."
                          },
                          "rate_annual": {
                            "type": "number",
                            "minimum": 0,
                            "exclusiveMaximum": 1,
                            "description": "Annual interest rate, e.g. 0.065."
                          },
                          "amortization_years": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 50,
                            "description": "Amortization in years with monthly payments; 0 means interest-only. Any balance outstanding at the horizon is repaid from sale proceeds."
                          }
                        },
                        "required": [
                          "amount",
                          "rate_annual",
                          "amortization_years"
                        ]
                      },
                      "operating_expenses_annual": {
                        "default": 0,
                        "description": "Year-1 operating expenses borne by the owner (utilities, repairs, management), USD per year.",
                        "type": "number",
                        "minimum": 0
                      },
                      "capex_reserve_annual": {
                        "default": 0,
                        "description": "Year-1 capital reserve, USD per year. Treated as a cash cost; not tax-deductible.",
                        "type": "number",
                        "minimum": 0
                      },
                      "property_taxes_annual": {
                        "default": 0,
                        "description": "Year-1 property taxes, USD per year.",
                        "type": "number",
                        "minimum": 0
                      },
                      "insurance_annual": {
                        "default": 0,
                        "description": "Year-1 property insurance, USD per year.",
                        "type": "number",
                        "minimum": 0
                      },
                      "expense_growth_rate_annual": {
                        "default": 0,
                        "description": "Annual growth applied to expenses after year 1, e.g. 0.03 (year k = year-1 amount × (1 + g)^(k − 1)).",
                        "type": "number",
                        "minimum": 0,
                        "exclusiveMaximum": 1
                      },
                      "residual": {
                        "oneOf": [
                          {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string",
                                "const": "value"
                              },
                              "amount": {
                                "type": "number",
                                "minimum": 0,
                                "description": "Expected gross sale price at the end of the horizon, USD."
                              }
                            },
                            "required": [
                              "type",
                              "amount"
                            ],
                            "description": "A stated residual value."
                          },
                          {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string",
                                "const": "exit_cap"
                              },
                              "cap_rate": {
                                "type": "number",
                                "exclusiveMinimum": 0,
                                "exclusiveMaximum": 1,
                                "description": "Exit capitalization rate, e.g. 0.07."
                              },
                              "stabilized_noi": {
                                "type": "number",
                                "minimum": 0,
                                "description": "Stabilized NOI the property would earn if leased at market in the year after the horizon, USD per year."
                              }
                            },
                            "required": [
                              "type",
                              "cap_rate",
                              "stabilized_noi"
                            ],
                            "description": "Residual = stabilized NOI ÷ exit cap rate (direct capitalization)."
                          }
                        ],
                        "description": "How the sale price at the horizon is set."
                      },
                      "selling_costs_rate": {
                        "default": 0,
                        "description": "Brokerage and closing costs on sale as a share of the sale price, e.g. 0.03.",
                        "type": "number",
                        "minimum": 0,
                        "exclusiveMaximum": 1
                      },
                      "tax": {
                        "description": "Optional after-tax comparison. When omitted (the default), both cases are compared pre-tax. Operating expenses, property taxes, and insurance are always deductible when this block is present; the gain on sale is taxed at the marginal rate (no separate recapture rate).",
                        "type": "object",
                        "properties": {
                          "marginal_tax_rate": {
                            "type": "number",
                            "minimum": 0,
                            "exclusiveMaximum": 1,
                            "description": "Combined marginal income tax rate applied to deductions and to the gain on sale, e.g. 0.26."
                          },
                          "depreciation_life_years": {
                            "default": 39,
                            "description": "Straight-line depreciation life for the building (39 for US non-residential real property). No mid-month convention.",
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 100
                          },
                          "land_share": {
                            "default": 0.2,
                            "description": "Share of purchase price plus closing costs allocated to land, which is not depreciated.",
                            "type": "number",
                            "minimum": 0,
                            "exclusiveMaximum": 1
                          },
                          "rent_deductible": {
                            "default": true,
                            "description": "Whether rent paid in the lease case is deductible (true for an operating business).",
                            "type": "boolean"
                          },
                          "interest_deductible": {
                            "default": true,
                            "description": "Whether loan interest in the own case is deductible.",
                            "type": "boolean"
                          },
                          "depreciation_deductible": {
                            "default": true,
                            "description": "Whether depreciation in the own case is deductible.",
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "marginal_tax_rate"
                        ]
                      }
                    },
                    "required": [
                      "purchase_price",
                      "residual"
                    ],
                    "description": "The purchase alternative."
                  }
                },
                "required": [
                  "horizon_years",
                  "discount_rate_annual",
                  "lease",
                  "own"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/renew_vs_relocate": {
      "post": {
        "operationId": "calc_renew_vs_relocate",
        "summary": "Renew vs. relocate",
        "description": "Compare renewing in place with moving over one horizon: NPV and discounted effective rent of each case (rent schedule with free rent, TI net of allowance, fees, moving, downtime, double rent, termination, and disruption costs), the starting rent at which they break even, and year-by-year cash flows. Deterministic: use this instead of doing the math yourself; cite formula calc.renew_vs_relocate@2 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "horizon_months": {
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "maximum": 600,
                    "description": "Analysis horizon in months; both cases run for this term (e.g. 120 for a ten-year renewal versus a ten-year new lease)."
                  },
                  "discount_rate_annual": {
                    "default": 0.08,
                    "description": "Annual discount rate as a decimal, e.g. 0.08, applied to both cases (monthly rate = annual ÷ 12).",
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1
                  },
                  "current_rent_monthly": {
                    "default": 0,
                    "description": "Total monthly rent under the existing lease, USD per month for the whole premises (not $/SF). Prices the double-rent overlap in the relocate case.",
                    "type": "number",
                    "minimum": 0
                  },
                  "renew": {
                    "type": "object",
                    "properties": {
                      "schedule": {
                        "type": "object",
                        "properties": {
                          "commencement_date": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                            "description": "Rent commencement date, YYYY-MM-DD. Month n starts on this day-of-month (clamped to month end)."
                          },
                          "area_sf": {
                            "type": "number",
                            "exclusiveMinimum": 0,
                            "description": "Rentable square feet, or the quantity leased in unit (kW, MW, units, acres, keys, rooms, spaces) when unit is set."
                          },
                          "starting_rent_psf_annual": {
                            "type": "number",
                            "minimum": 0,
                            "description": "Year-one base rent in dollars per SF per year (annual, not monthly), e.g. 42.5; per unit per year when unit is set (a $/kW/month quote × 12)."
                          },
                          "escalation": {
                            "default": {
                              "type": "none"
                            },
                            "description": "How base rent escalates. Omit for flat rent.",
                            "oneOf": [
                              {
                                "type": "object",
                                "properties": {
                                  "type": {
                                    "type": "string",
                                    "const": "none"
                                  }
                                },
                                "required": [
                                  "type"
                                ],
                                "description": "Flat rent for the whole term."
                              },
                              {
                                "type": "object",
                                "properties": {
                                  "type": {
                                    "type": "string",
                                    "const": "percent"
                                  },
                                  "rate": {
                                    "type": "number",
                                    "exclusiveMinimum": -1,
                                    "exclusiveMaximum": 10,
                                    "description": "Bump as a decimal, e.g. 0.03 for 3%. Bumps compound."
                                  },
                                  "every_months": {
                                    "default": 12,
                                    "description": "Months between bumps (12 = annual). The first bump takes effect in month every_months + 1.",
                                    "type": "integer",
                                    "exclusiveMinimum": 0,
                                    "maximum": 9007199254740991
                                  }
                                },
                                "required": [
                                  "type",
                                  "rate"
                                ],
                                "description": "Percentage bumps: rate_k = starting_rate × (1 + rate)^k."
                              },
                              {
                                "type": "object",
                                "properties": {
                                  "type": {
                                    "type": "string",
                                    "const": "fixed_psf"
                                  },
                                  "amount": {
                                    "type": "number",
                                    "description": "Dollars per SF per year (or per unit per year when unit is set) added at each bump, e.g. 0.50."
                                  },
                                  "every_months": {
                                    "default": 12,
                                    "description": "Months between bumps (12 = annual). The first bump takes effect in month every_months + 1.",
                                    "type": "integer",
                                    "exclusiveMinimum": 0,
                                    "maximum": 9007199254740991
                                  }
                                },
                                "required": [
                                  "type",
                                  "amount"
                                ],
                                "description": "Fixed $/SF bumps: rate_k = starting_rate + amount × k."
                              }
                            ]
                          },
                          "abated_months": {
                            "default": [],
                            "description": "Fully abated (free) months, 1-based from commencement (e.g. [1,2,3] for three months free up front). Each listed month's base rent is zero.",
                            "maxItems": 1200,
                            "type": "array",
                            "items": {
                              "type": "integer",
                              "exclusiveMinimum": 0,
                              "maximum": 9007199254740991
                            }
                          },
                          "round_rate_to_cents": {
                            "default": false,
                            "description": "Round each step's $/SF/yr to cents, as many lease rent tables do.",
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "commencement_date",
                          "area_sf",
                          "starting_rent_psf_annual"
                        ],
                        "description": "Renewal rent terms: commencement date, area, starting $/SF/yr, escalation, and abated (free) months. The term is horizon_months."
                      },
                      "ti_cost": {
                        "default": 0,
                        "description": "Refurbishment or TI the tenant would spend on the current premises at renewal, USD, before any allowance.",
                        "type": "number",
                        "minimum": 0
                      },
                      "ti_allowance": {
                        "default": 0,
                        "description": "Landlord renewal allowance, USD. Offsets ti_cost; any excess is not paid out.",
                        "type": "number",
                        "minimum": 0
                      },
                      "renewal_fee": {
                        "default": 0,
                        "description": "Fees to renew (legal, brokerage paid by tenant), USD.",
                        "type": "number",
                        "minimum": 0
                      }
                    },
                    "required": [
                      "schedule"
                    ],
                    "description": "Stay and renew the existing lease."
                  },
                  "relocate": {
                    "type": "object",
                    "properties": {
                      "schedule": {
                        "type": "object",
                        "properties": {
                          "commencement_date": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                            "description": "Rent commencement date, YYYY-MM-DD. Month n starts on this day-of-month (clamped to month end)."
                          },
                          "area_sf": {
                            "type": "number",
                            "exclusiveMinimum": 0,
                            "description": "Rentable square feet, or the quantity leased in unit (kW, MW, units, acres, keys, rooms, spaces) when unit is set."
                          },
                          "starting_rent_psf_annual": {
                            "type": "number",
                            "minimum": 0,
                            "description": "Year-one base rent in dollars per SF per year (annual, not monthly), e.g. 42.5; per unit per year when unit is set (a $/kW/month quote × 12)."
                          },
                          "escalation": {
                            "default": {
                              "type": "none"
                            },
                            "description": "How base rent escalates. Omit for flat rent.",
                            "oneOf": [
                              {
                                "type": "object",
                                "properties": {
                                  "type": {
                                    "type": "string",
                                    "const": "none"
                                  }
                                },
                                "required": [
                                  "type"
                                ],
                                "description": "Flat rent for the whole term."
                              },
                              {
                                "type": "object",
                                "properties": {
                                  "type": {
                                    "type": "string",
                                    "const": "percent"
                                  },
                                  "rate": {
                                    "type": "number",
                                    "exclusiveMinimum": -1,
                                    "exclusiveMaximum": 10,
                                    "description": "Bump as a decimal, e.g. 0.03 for 3%. Bumps compound."
                                  },
                                  "every_months": {
                                    "default": 12,
                                    "description": "Months between bumps (12 = annual). The first bump takes effect in month every_months + 1.",
                                    "type": "integer",
                                    "exclusiveMinimum": 0,
                                    "maximum": 9007199254740991
                                  }
                                },
                                "required": [
                                  "type",
                                  "rate"
                                ],
                                "description": "Percentage bumps: rate_k = starting_rate × (1 + rate)^k."
                              },
                              {
                                "type": "object",
                                "properties": {
                                  "type": {
                                    "type": "string",
                                    "const": "fixed_psf"
                                  },
                                  "amount": {
                                    "type": "number",
                                    "description": "Dollars per SF per year (or per unit per year when unit is set) added at each bump, e.g. 0.50."
                                  },
                                  "every_months": {
                                    "default": 12,
                                    "description": "Months between bumps (12 = annual). The first bump takes effect in month every_months + 1.",
                                    "type": "integer",
                                    "exclusiveMinimum": 0,
                                    "maximum": 9007199254740991
                                  }
                                },
                                "required": [
                                  "type",
                                  "amount"
                                ],
                                "description": "Fixed $/SF bumps: rate_k = starting_rate + amount × k."
                              }
                            ]
                          },
                          "abated_months": {
                            "default": [],
                            "description": "Fully abated (free) months, 1-based from commencement (e.g. [1,2,3] for three months free up front). Each listed month's base rent is zero.",
                            "maxItems": 1200,
                            "type": "array",
                            "items": {
                              "type": "integer",
                              "exclusiveMinimum": 0,
                              "maximum": 9007199254740991
                            }
                          },
                          "round_rate_to_cents": {
                            "default": false,
                            "description": "Round each step's $/SF/yr to cents, as many lease rent tables do.",
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "commencement_date",
                          "area_sf",
                          "starting_rent_psf_annual"
                        ],
                        "description": "New-premises rent terms: commencement date, area, starting $/SF/yr, escalation, and abated (free) months. The term is horizon_months."
                      },
                      "buildout_cost": {
                        "default": 0,
                        "description": "Total cost to build out the new premises, USD, before any allowance.",
                        "type": "number",
                        "minimum": 0
                      },
                      "ti_allowance": {
                        "default": 0,
                        "description": "Landlord TI allowance at the new premises, USD. Offsets buildout_cost; any excess is not paid out.",
                        "type": "number",
                        "minimum": 0
                      },
                      "moving_cost": {
                        "default": 0,
                        "description": "Moving, IT, cabling, signage, and stationery costs, USD.",
                        "type": "number",
                        "minimum": 0
                      },
                      "downtime_months": {
                        "default": 0,
                        "description": "Months the business runs at reduced capacity around the move.",
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 9007199254740991
                      },
                      "downtime_cost_monthly": {
                        "default": 0,
                        "description": "Cost of a month of downtime (lost margin, temporary space), USD per month. Multiplied by downtime_months.",
                        "type": "number",
                        "minimum": 0
                      },
                      "overlap_months": {
                        "default": 0,
                        "description": "Months of double rent: the old premises are still paid (at current_rent_monthly) after the new lease starts.",
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 9007199254740991
                      },
                      "termination_cost": {
                        "default": 0,
                        "description": "Early termination fee, holdover premium, or restoration obligation at the old premises, USD.",
                        "type": "number",
                        "minimum": 0
                      },
                      "disruption_cost": {
                        "default": 0,
                        "description": "Explicit one-time cost of business disruption beyond rent and downtime (lost customers, staff turnover), USD.",
                        "type": "number",
                        "minimum": 0
                      }
                    },
                    "required": [
                      "schedule"
                    ],
                    "description": "Move to new premises."
                  }
                },
                "required": [
                  "horizon_months",
                  "renew",
                  "relocate"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/hotel_metrics": {
      "post": {
        "operationId": "calc_hotel_metrics",
        "summary": "Hotel operating metrics (USALI)",
        "description": "Occupancy, ADR, RevPAR, TRevPAR, and a USALI-style profit ladder for a period: departmental profit, GOP and margin, GOPPAR, management fees, EBITDA, FF&E reserve, and NOI (EBITDA less replacement reserve). Deterministic: use this instead of doing the math yourself; cite formula calc.hotel_metrics@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nights": {
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "maximum": 9007199254740991,
                    "description": "Nights in the period, e.g. 365 for a year or 30 for a month."
                  },
                  "rooms": {
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "maximum": 9007199254740991,
                    "description": "Rooms in inventory (keys)."
                  },
                  "rooms_out_of_order": {
                    "default": 0,
                    "description": "Room-nights (rooms × nights, not rooms) taken off the market in the period, e.g. 20 rooms closed for a 30-night renovation = 600. Subtracted from rooms × nights to give rooms available. USALI (11th ed.) keeps short-term out-of-order rooms in rooms available; include only rooms formally removed from sale.",
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "rooms_sold": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991,
                    "description": "Room-nights occupied by paying guests in the period (USALI: rooms sold, excluding complimentary rooms)."
                  },
                  "room_revenue": {
                    "type": "number",
                    "minimum": 0,
                    "description": "Rooms revenue for the period, USD (USALI Rooms department revenue, net of taxes and allowances)."
                  },
                  "fb_revenue": {
                    "default": 0,
                    "description": "Food and beverage revenue, USD (USALI Food and Beverage department revenue).",
                    "type": "number",
                    "minimum": 0
                  },
                  "other_revenue": {
                    "default": 0,
                    "description": "Other operated departments plus miscellaneous income, USD (USALI Other Operated Departments and Miscellaneous Income).",
                    "type": "number",
                    "minimum": 0
                  },
                  "departmental_expenses": {
                    "default": 0,
                    "description": "Total departmental expenses, USD: rooms, F&B, and other operated departments (USALI Total Departmental Expenses).",
                    "type": "number",
                    "minimum": 0
                  },
                  "undistributed_expenses": {
                    "default": 0,
                    "description": "Total undistributed operating expenses, USD: administrative and general, information and telecommunications, sales and marketing, property operation and maintenance, utilities (USALI Total Undistributed Operating Expenses).",
                    "type": "number",
                    "minimum": 0
                  },
                  "management_fee_rate": {
                    "default": 0,
                    "description": "Base management fee as a share of total operating revenue, e.g. 0.03 (USALI Management Fees).",
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1
                  },
                  "management_fee_fixed": {
                    "default": 0,
                    "description": "Fixed or incentive management fee for the period, USD, added to the percentage fee (USALI Management Fees).",
                    "type": "number",
                    "minimum": 0
                  },
                  "non_operating_expenses": {
                    "default": 0,
                    "description": "Property taxes, insurance, ground rent, and other non-operating expenses, USD (USALI Non-Operating Income and Expenses).",
                    "type": "number",
                    "minimum": 0
                  },
                  "ffe_reserve_rate": {
                    "default": 0,
                    "description": "FF&E replacement reserve as a share of total operating revenue, e.g. 0.04 (USALI Replacement Reserve, below EBITDA).",
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1
                  }
                },
                "required": [
                  "nights",
                  "rooms",
                  "rooms_sold",
                  "room_revenue"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/dcf_pro_forma": {
      "post": {
        "operationId": "calc_dcf_pro_forma",
        "summary": "Property DCF from a rent roll",
        "description": "Property-level discounted cash flow from an in-place rent roll: each lease's rent, escalations, remaining term, and expense recovery (NNN, gross, base year), then at expiration one blended lease weighted by the renewal probability (renewal vs new-tenant rent, free rent, TI, leasing commissions, and new-tenant downtime, with fractional downtime and free rent kept as part months rather than rounded); market rent growth, general vacancy and credit loss, operating expenses, management fee, and capital reserves. Returns the annual cash-flow table (NOI and cash flow before debt) for the hold plus one year, the exit value at a cap rate on forward (or trailing) NOI less selling costs, the value (PV at the discount rate, end-of-year or mid-year), and with a purchase price the NPV, unlevered IRR, and equity multiple, with every lease event and convention stated. Deterministic: use this instead of doing the math yourself; cite formula calc.dcf_pro_forma@2 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hold_years": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 30,
                    "description": "Holding period in years. The property is sold at the end of the last year; one more year is projected for the forward NOI."
                  },
                  "leases": {
                    "minItems": 1,
                    "maxItems": 200,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200,
                          "description": "Suite or tenant label, e.g. \"Suite 200\"."
                        },
                        "area_sf": {
                          "type": "number",
                          "exclusiveMinimum": 0,
                          "description": "Leased area, SF."
                        },
                        "rent_psf_annual": {
                          "type": "number",
                          "minimum": 0,
                          "description": "Current in-place base rent, USD/SF/yr (0 for space vacant at the start)."
                        },
                        "months_remaining": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 600,
                          "description": "Months left on the lease from the analysis start: the lease runs through this month (1 = expires at the end of month 1). 0 means the space is vacant at the start and is leased to a new tenant after downtime_months."
                        },
                        "escalation_rate": {
                          "default": 0,
                          "description": "Annual bump in the in-place rent, e.g. 0.03, applied first in next_escalation_month and every 12 months after.",
                          "type": "number",
                          "minimum": 0,
                          "exclusiveMaximum": 1
                        },
                        "next_escalation_month": {
                          "default": 13,
                          "description": "Analysis month of the next in-place rent bump (default 13, the start of year 2). Use the lease's anniversary month.",
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 600
                        },
                        "free_rent_months_remaining": {
                          "default": 0,
                          "description": "Months of in-place free base rent still to run from the analysis start (months 1 to this are abated).",
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 600
                        },
                        "recovery": {
                          "default": "nnn",
                          "description": "In-place expense recovery: \"nnn\", \"gross\", or \"base_year\" (needs base_year_opex_psf_annual).",
                          "type": "string",
                          "enum": [
                            "nnn",
                            "gross",
                            "base_year"
                          ]
                        },
                        "base_year_opex_psf_annual": {
                          "description": "Operating expenses in the lease's base year, USD/SF/yr: the tenant pays the excess above it. Required when recovery is base_year.",
                          "type": "number",
                          "minimum": 0
                        },
                        "market_rent_psf_annual": {
                          "type": "number",
                          "minimum": 0,
                          "description": "Market rent for this space today, USD/SF/yr. Grows at market_rent_growth_rate each analysis year."
                        },
                        "leasing": {
                          "type": "object",
                          "properties": {
                            "renewal_probability": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1,
                              "description": "Probability the tenant renews at expiration, 0 to 1 (e.g. 0.65). Blends the renewal and new-tenant assumptions below."
                            },
                            "lease_term_months": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 240,
                              "description": "Term of each lease signed at rollover (renewal or new tenant), months."
                            },
                            "downtime_months": {
                              "type": "integer",
                              "minimum": 0,
                              "maximum": 60,
                              "description": "Months vacant before a new tenant starts paying (no downtime is assumed on renewal). Blended downtime = (1 − renewal_probability) × this, kept fractional, not rounded: the suite is vacant for the whole months and the rollover lease's first month collects 1 − the fraction of its rent and recoveries (0.45 months of downtime: the first month collects 55%)."
                            },
                            "renewal_rent_pct_of_market": {
                              "type": "number",
                              "exclusiveMinimum": 0,
                              "maximum": 2,
                              "description": "Renewal rent as a share of market rent, e.g. 0.95 for a 5% renewal discount; new tenants pay market rent."
                            },
                            "rent_escalation_rate": {
                              "type": "number",
                              "minimum": 0,
                              "exclusiveMaximum": 1,
                              "description": "Annual bump in a rollover lease's base rent, compounding every 12 months from its start, e.g. 0.03."
                            },
                            "free_rent_months_new": {
                              "type": "integer",
                              "minimum": 0,
                              "maximum": 60,
                              "description": "Months of free base rent for a new tenant at the start of its lease."
                            },
                            "free_rent_months_renewal": {
                              "type": "integer",
                              "minimum": 0,
                              "maximum": 60,
                              "description": "Months of free base rent on a renewal. Blended free rent = p × this + (1 − p) × free_rent_months_new, kept fractional: the whole months are fully abated and the next month by the fraction."
                            },
                            "ti_psf_new": {
                              "type": "number",
                              "minimum": 0,
                              "description": "Tenant improvement allowance for a new tenant, USD/SF in today's dollars (grown at market_rent_growth_rate to the lease start)."
                            },
                            "ti_psf_renewal": {
                              "type": "number",
                              "minimum": 0,
                              "description": "Tenant improvement allowance on a renewal, USD/SF in today's dollars."
                            },
                            "lc_rate_new": {
                              "type": "number",
                              "minimum": 0,
                              "exclusiveMaximum": 1,
                              "description": "Leasing commission for a new tenant as a share of the base rent payable over the lease term after free rent, e.g. 0.06."
                            },
                            "lc_rate_renewal": {
                              "type": "number",
                              "minimum": 0,
                              "exclusiveMaximum": 1,
                              "description": "Leasing commission on a renewal, same basis, e.g. 0.03."
                            },
                            "recovery": {
                              "type": "string",
                              "enum": [
                                "nnn",
                                "gross",
                                "base_year"
                              ],
                              "description": "Expense recovery on rollover leases: \"nnn\" (tenant pays its share of operating expenses), \"gross\" (none), or \"base_year\" (expenses above the lease's first-year level)."
                            }
                          },
                          "description": "Per-lease overrides of market_leasing (any subset of its fields)."
                        }
                      },
                      "required": [
                        "name",
                        "area_sf",
                        "rent_psf_annual",
                        "months_remaining",
                        "market_rent_psf_annual"
                      ]
                    },
                    "description": "Rent roll: one entry per lease or vacant suite (at most 200). The building area is the sum of their areas."
                  },
                  "market_leasing": {
                    "type": "object",
                    "properties": {
                      "renewal_probability": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 1,
                        "description": "Probability the tenant renews at expiration, 0 to 1 (e.g. 0.65). Blends the renewal and new-tenant assumptions below."
                      },
                      "lease_term_months": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 240,
                        "description": "Term of each lease signed at rollover (renewal or new tenant), months."
                      },
                      "downtime_months": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 60,
                        "description": "Months vacant before a new tenant starts paying (no downtime is assumed on renewal). Blended downtime = (1 − renewal_probability) × this, kept fractional, not rounded: the suite is vacant for the whole months and the rollover lease's first month collects 1 − the fraction of its rent and recoveries (0.45 months of downtime: the first month collects 55%)."
                      },
                      "renewal_rent_pct_of_market": {
                        "default": 1,
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "maximum": 2,
                        "description": "Renewal rent as a share of market rent, e.g. 0.95 for a 5% renewal discount; new tenants pay market rent."
                      },
                      "rent_escalation_rate": {
                        "default": 0,
                        "type": "number",
                        "minimum": 0,
                        "exclusiveMaximum": 1,
                        "description": "Annual bump in a rollover lease's base rent, compounding every 12 months from its start, e.g. 0.03."
                      },
                      "free_rent_months_new": {
                        "default": 0,
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 60,
                        "description": "Months of free base rent for a new tenant at the start of its lease."
                      },
                      "free_rent_months_renewal": {
                        "default": 0,
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 60,
                        "description": "Months of free base rent on a renewal. Blended free rent = p × this + (1 − p) × free_rent_months_new, kept fractional: the whole months are fully abated and the next month by the fraction."
                      },
                      "ti_psf_new": {
                        "default": 0,
                        "type": "number",
                        "minimum": 0,
                        "description": "Tenant improvement allowance for a new tenant, USD/SF in today's dollars (grown at market_rent_growth_rate to the lease start)."
                      },
                      "ti_psf_renewal": {
                        "default": 0,
                        "type": "number",
                        "minimum": 0,
                        "description": "Tenant improvement allowance on a renewal, USD/SF in today's dollars."
                      },
                      "lc_rate_new": {
                        "default": 0,
                        "type": "number",
                        "minimum": 0,
                        "exclusiveMaximum": 1,
                        "description": "Leasing commission for a new tenant as a share of the base rent payable over the lease term after free rent, e.g. 0.06."
                      },
                      "lc_rate_renewal": {
                        "default": 0,
                        "type": "number",
                        "minimum": 0,
                        "exclusiveMaximum": 1,
                        "description": "Leasing commission on a renewal, same basis, e.g. 0.03."
                      },
                      "recovery": {
                        "default": "nnn",
                        "type": "string",
                        "enum": [
                          "nnn",
                          "gross",
                          "base_year"
                        ],
                        "description": "Expense recovery on rollover leases: \"nnn\" (tenant pays its share of operating expenses), \"gross\" (none), or \"base_year\" (expenses above the lease's first-year level)."
                      }
                    },
                    "required": [
                      "renewal_probability",
                      "lease_term_months",
                      "downtime_months"
                    ],
                    "description": "Market leasing assumptions applied at every lease expiration (and to space vacant at the start). A lease's own `leasing` object overrides any of them."
                  },
                  "market_rent_growth_rate": {
                    "default": 0,
                    "description": "Annual market rent growth, e.g. 0.03. Market rent and TI in analysis year y = today's × (1 + g)^(y − 1).",
                    "type": "number",
                    "exclusiveMinimum": -1,
                    "exclusiveMaximum": 1
                  },
                  "operating_expenses_psf_annual": {
                    "default": 0,
                    "description": "Year-1 recoverable operating expenses, USD/SF/yr over the whole building area.",
                    "type": "number",
                    "minimum": 0
                  },
                  "non_recoverable_expenses_annual": {
                    "default": 0,
                    "description": "Year-1 operating expenses no tenant reimburses, USD/yr.",
                    "type": "number",
                    "minimum": 0
                  },
                  "expense_growth_rate": {
                    "default": 0,
                    "description": "Annual growth of operating expenses and capital reserves, e.g. 0.03: year y = year 1 × (1 + g)^(y − 1).",
                    "type": "number",
                    "exclusiveMinimum": -1,
                    "exclusiveMaximum": 1
                  },
                  "management_fee_rate": {
                    "default": 0,
                    "description": "Management fee as a share of effective gross revenue, e.g. 0.03; not recovered.",
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1
                  },
                  "general_vacancy_rate": {
                    "default": 0,
                    "description": "General vacancy allowance as a share of gross revenue (base rent after free rent, plus recoveries), on top of the downtime modelled at rollover.",
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1
                  },
                  "credit_loss_rate": {
                    "default": 0,
                    "description": "Credit loss as a share of gross revenue, on top of general vacancy.",
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1
                  },
                  "capital_reserve_psf_annual": {
                    "default": 0,
                    "description": "Year-1 capital reserve, USD/SF/yr over the building area, below NOI. Grows with expense_growth_rate.",
                    "type": "number",
                    "minimum": 0
                  },
                  "exit_cap_rate": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "exclusiveMaximum": 1,
                    "description": "Exit (terminal) cap rate applied to the exit NOI, e.g. 0.07."
                  },
                  "exit_noi_basis": {
                    "default": "forward",
                    "description": "\"forward\": the NOI of the year after the hold (the usual convention); \"trailing\": the NOI of the final hold year.",
                    "type": "string",
                    "enum": [
                      "forward",
                      "trailing"
                    ]
                  },
                  "selling_cost_rate": {
                    "default": 0,
                    "description": "Selling costs as a share of the gross sale price, e.g. 0.02.",
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1
                  },
                  "discount_rate": {
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1,
                    "description": "Annual unlevered discount rate, e.g. 0.08. The present value at this rate is the value."
                  },
                  "timing": {
                    "default": "end_of_year",
                    "description": "\"end_of_year\": each year's cash flow is discounted (1 + r)^y (Excel NPV). \"mid_year\": operating cash flows at (1 + r)^(y − 0.5); the sale is always discounted at the end of the hold.",
                    "type": "string",
                    "enum": [
                      "end_of_year",
                      "mid_year"
                    ]
                  },
                  "purchase_price": {
                    "description": "Price paid at the start, USD. Adds the NPV (value − price), the unlevered IRR and the going-in cap rate on price.",
                    "type": "number",
                    "exclusiveMinimum": 0
                  }
                },
                "required": [
                  "hold_years",
                  "leases",
                  "market_leasing",
                  "exit_cap_rate",
                  "discount_rate"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/direct_cap": {
      "post": {
        "operationId": "calc_direct_cap",
        "summary": "Direct capitalization value and implied cap rate",
        "description": "Value by direct capitalization: (NOI − capital reserve) ÷ cap rate, then below-the-line deductions for lease-up costs, free-rent burn-off, and other adjustments to an as-is value (optionally rounded); and the cap rate a price implies, before and after reserves and all-in after the below-the-line costs. Records the NOI basis (trailing, forward, stabilized). Deterministic: use this instead of doing the math yourself; cite formula calc.direct_cap@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "noi": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "Net operating income to capitalize, USD/yr, before capital reserves (they are deducted separately below)."
                  },
                  "noi_basis": {
                    "description": "What the NOI is: trailing 12 months (T-12), forward 12 months (year 1 of a pro forma), or stabilized. The cap rate must be on the same basis; this is recorded in the steps, not checked.",
                    "type": "string",
                    "enum": [
                      "trailing_12",
                      "forward_12",
                      "stabilized"
                    ]
                  },
                  "cap_rate": {
                    "description": "Capitalization rate as a decimal, e.g. 0.065 for 6.5%. Gives the value. Provide cap_rate, price, or both.",
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "exclusiveMaximum": 1
                  },
                  "price": {
                    "description": "Purchase or asking price, USD. Gives the implied cap rate. Provide cap_rate, price, or both.",
                    "type": "number",
                    "exclusiveMinimum": 0
                  },
                  "capex_reserve_annual": {
                    "default": 0,
                    "description": "Annual capital (replacement) reserve deducted from NOI before capitalizing, USD/yr. 0 if the NOI is already after reserves.",
                    "type": "number",
                    "minimum": 0
                  },
                  "lease_up_costs": {
                    "default": 0,
                    "description": "One-time costs to reach the stabilized NOI (TI, leasing commissions, lost rent during lease-up), USD, deducted from the capitalized value.",
                    "type": "number",
                    "minimum": 0
                  },
                  "free_rent_burn_off": {
                    "default": 0,
                    "description": "Free rent still to be given under signed leases (rent the stabilized NOI assumes but the owner will not collect), USD, deducted from the capitalized value.",
                    "type": "number",
                    "minimum": 0
                  },
                  "other_adjustments": {
                    "default": [],
                    "description": "Further below-the-line value adjustments, applied after capitalizing.",
                    "maxItems": 20,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "label": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200,
                          "description": "What the adjustment is, e.g. \"deferred maintenance\" or \"excess land\"."
                        },
                        "amount": {
                          "type": "number",
                          "description": "USD added to the value: negative for a deduction (deferred maintenance), positive for an addition (excess land)."
                        }
                      },
                      "required": [
                        "label",
                        "amount"
                      ]
                    }
                  },
                  "area_sf": {
                    "description": "Rentable area in SF, for value per SF. Optional.",
                    "type": "number",
                    "exclusiveMinimum": 0
                  },
                  "round_value_to": {
                    "description": "Also report the as-is value rounded to this increment (half away from zero), e.g. 100000 as appraisers round. Optional.",
                    "type": "number",
                    "exclusiveMinimum": 0
                  }
                },
                "required": [
                  "noi"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/amortization_schedule": {
      "post": {
        "operationId": "calc_amortization_schedule",
        "summary": "Loan amortization schedule",
        "description": "Period-by-period schedule of a fixed-rate loan, monthly or quarterly: an optional interest-only period, then level payments on an amortization term that can run past maturity (balloon), with 30/360, actual/360, or actual/365 interest (the payment on a 30/360 basis). Rows are in cents that foot: interest is rounded each period, the final payment repays the whole balance, and the totals are the row sums. Deterministic: use this instead of doing the math yourself; cite formula calc.amortization_schedule@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "principal": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "Loan amount funded, USD."
                  },
                  "rate_annual": {
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1,
                    "description": "Fixed nominal annual interest rate as a decimal, e.g. 0.065 for 6.5%."
                  },
                  "payment_frequency": {
                    "default": "monthly",
                    "description": "Payment period: monthly (12 a year) or quarterly (4 a year). Term, amortization, and interest-only months must be whole periods.",
                    "type": "string",
                    "enum": [
                      "monthly",
                      "quarterly"
                    ]
                  },
                  "term_months": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 600,
                    "description": "Months from funding to maturity, when any remaining balance (the balloon) is repaid."
                  },
                  "amortization_months": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 600,
                    "description": "Amortization period in months that sets the level payment after any interest-only period, e.g. 360 for a 30-year schedule; 0 means interest-only to maturity. Must be at least term_months − interest_only_months (a shorter schedule would repay the loan before maturity)."
                  },
                  "interest_only_months": {
                    "default": 0,
                    "description": "Months of interest-only payments at the start of the loan; amortization over amortization_months begins after them. At most term_months.",
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 600
                  },
                  "day_count": {
                    "default": "30/360",
                    "description": "Interest day count: \"30/360\" (each period is 30 days a month: interest = balance × rate ÷ periods a year), \"actual/360\" or \"actual/365\" (balance × rate × actual days in the period ÷ 360 or 365; needs start_date). Under actual day counts the level payment is still computed on a 30/360 basis, as US commercial loans usually are, so the balloon absorbs the difference.",
                    "type": "string",
                    "enum": [
                      "30/360",
                      "actual/360",
                      "actual/365"
                    ]
                  },
                  "start_date": {
                    "description": "Funding date, YYYY-MM-DD. Payment k falls k periods later on the same day of the month (clamped to month end); the first period runs from this date to the first payment date. Required for actual/360 and actual/365; optional for 30/360, where it only dates the rows.",
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                  }
                },
                "required": [
                  "principal",
                  "rate_annual",
                  "term_months",
                  "amortization_months"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/equity_waterfall": {
      "post": {
        "operationId": "calc_equity_waterfall",
        "summary": "LP/GP equity distribution waterfall",
        "description": "Split periodic project equity cash flows between LP and GP: preferred return (compounding or simple) and return of capital pro rata to capital in either order, an optional GP catch-up, IRR-hurdle tiers with promote splits measured on the LP's IRR, and a residual split. Returns per-period LP and GP contributions and distributions (with the GP's promote) that foot to the cent, and LP, GP, and project IRRs and multiples, with the conventions to confirm against the operating agreement. Deterministic: use this instead of doing the math yourself; cite formula calc.equity_waterfall@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cash_flows": {
                    "minItems": 2,
                    "maxItems": 600,
                    "type": "array",
                    "items": {
                      "type": "number"
                    },
                    "description": "Project-level equity cash flows at t = 0, 1, 2, … periods: negative = capital contributed by the investors, positive = cash available to distribute. At most 600."
                  },
                  "periods_per_year": {
                    "default": 1,
                    "description": "Cash-flow periods per year: 1 annual, 4 quarterly, 12 monthly. Sets the pref accrual per period and annualizes the IRRs.",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 12
                  },
                  "lp_share": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "maximum": 1,
                    "description": "Share of every contribution funded by the LP, e.g. 0.9; the GP co-invests the rest (1 for no GP capital). Capital, pref, and the non-promote part of every split are shared in this ratio."
                  },
                  "pref": {
                    "description": "Preferred return on unreturned capital, paid to all investors pro rata to capital. Omit for none.",
                    "type": "object",
                    "properties": {
                      "rate_annual": {
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "exclusiveMaximum": 1,
                        "description": "Preferred return, annual, e.g. 0.08."
                      },
                      "compounding": {
                        "default": "compound",
                        "description": "\"compound\": unpaid pref earns pref, at the effective periodic rate (1 + rate)^(1 ÷ periods_per_year) − 1, so a fully paid pref is an IRR of rate on the capital. \"simple\": each period accrues unreturned capital × rate ÷ periods_per_year, and unpaid pref earns nothing.",
                        "type": "string",
                        "enum": [
                          "compound",
                          "simple"
                        ]
                      }
                    },
                    "required": [
                      "rate_annual"
                    ]
                  },
                  "distribution_order": {
                    "default": "pref_then_capital",
                    "description": "Which comes first from each distribution: accrued pref, then return of capital (the usual order), or capital first.",
                    "type": "string",
                    "enum": [
                      "pref_then_capital",
                      "capital_then_pref"
                    ]
                  },
                  "catch_up": {
                    "description": "GP catch-up after the pref and return of capital. Omit for none.",
                    "type": "object",
                    "properties": {
                      "gp_share": {
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "maximum": 1,
                        "description": "Share of each catch-up dollar paid to the GP as promote, e.g. 1 for a full catch-up or 0.5 for a 50% catch-up; the rest goes to the investors pro rata."
                      },
                      "target_promote": {
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "exclusiveMaximum": 1,
                        "description": "Catch-up continues until the GP's promote is this share of all profit distributed so far (everything other than return of capital), e.g. 0.2. Must be below gp_share."
                      }
                    },
                    "required": [
                      "gp_share",
                      "target_promote"
                    ]
                  },
                  "tiers": {
                    "default": [],
                    "description": "IRR-hurdle tiers after the catch-up, in increasing until_irr order: each distributes with its promote until the LP's IRR reaches until_irr.",
                    "maxItems": 10,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "until_irr": {
                          "type": "number",
                          "exclusiveMinimum": 0,
                          "exclusiveMaximum": 10,
                          "description": "LP IRR hurdle (annual, as calc_irr annualizes) that ends this tier, e.g. 0.12."
                        },
                        "promote": {
                          "type": "number",
                          "minimum": 0,
                          "exclusiveMaximum": 1,
                          "description": "Share of each dollar in this tier paid to the GP as promote, e.g. 0.2 for an 80/20 split; the rest goes to the investors pro rata to capital."
                        }
                      },
                      "required": [
                        "until_irr",
                        "promote"
                      ]
                    }
                  },
                  "residual_promote": {
                    "default": 0,
                    "description": "GP promote share of everything after the last tier, e.g. 0.3 for a 70/30 split.",
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1
                  }
                },
                "required": [
                  "cash_flows",
                  "lp_share"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/exchange_1031": {
      "post": {
        "operationId": "calc_exchange_1031",
        "summary": "1031 exchange deadlines, identification, and boot",
        "description": "Not tax advice: arithmetic only. The 45-day identification and 180-day exchange deadlines of IRC §1031(a)(3) (the exchange period ends earlier at a given tax-return due date), the three-property, 200%, and 95% identification tests of Treas. Reg. §1.1031(k)-1(c)(4), and the cash boot, mortgage boot, debt replacement, and trade-down of an exchange, with the boot-limited gain when the basis is given. Deterministic: use this instead of doing the math yourself; cite formula calc.exchange_1031@2 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "relinquished_transfer_date": {
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                    "description": "Date the relinquished property was transferred (closed), YYYY-MM-DD. With several relinquished properties, the earliest transfer starts both periods."
                  },
                  "tax_return_due_date": {
                    "description": "Due date, including extensions, of the taxpayer's federal return for the year of the transfer, YYYY-MM-DD. IRC §1031(a)(3)(B) ends the exchange period on this date if it is earlier than day 180. FactLot does not compute it.",
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                  },
                  "as_of": {
                    "description": "Date to count the days remaining from, YYYY-MM-DD. Optional.",
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                  },
                  "relinquished": {
                    "type": "object",
                    "properties": {
                      "sale_price": {
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "description": "Contract sale price (fair market value) of the relinquished property, USD. Also the base of the 200% identification test."
                      },
                      "selling_expenses": {
                        "default": 0,
                        "description": "Exchange and closing expenses paid at the sale (commissions, title, QI fees), USD.",
                        "type": "number",
                        "minimum": 0
                      },
                      "debt_relieved": {
                        "default": 0,
                        "description": "Mortgages and other liabilities paid off or assumed by the buyer at the sale, USD.",
                        "type": "number",
                        "minimum": 0
                      },
                      "adjusted_basis": {
                        "description": "Taxpayer's adjusted basis in the relinquished property, USD. Optional; adds the realized gain and the boot-limited gain.",
                        "type": "number",
                        "minimum": 0
                      }
                    },
                    "required": [
                      "sale_price"
                    ],
                    "description": "The property given up."
                  },
                  "cash_received_at_sale": {
                    "default": 0,
                    "description": "Cash the taxpayer took from the sale proceeds instead of leaving them with the qualified intermediary, USD.",
                    "type": "number",
                    "minimum": 0
                  },
                  "replacement": {
                    "description": "The property acquired (in total). Omit to check only the deadlines and identification.",
                    "type": "object",
                    "properties": {
                      "purchase_price": {
                        "type": "number",
                        "minimum": 0,
                        "description": "Total price of the replacement property or properties acquired, USD."
                      },
                      "acquisition_costs": {
                        "default": 0,
                        "description": "Closing costs paid at the purchase, USD.",
                        "type": "number",
                        "minimum": 0
                      },
                      "new_debt": {
                        "default": 0,
                        "description": "New or assumed mortgage debt on the replacement property, USD.",
                        "type": "number",
                        "minimum": 0
                      }
                    },
                    "required": [
                      "purchase_price"
                    ]
                  },
                  "identified_properties": {
                    "default": [],
                    "description": "Replacement properties identified in writing. Empty to skip the identification checks.",
                    "maxItems": 100,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200,
                          "description": "Label for the identified property."
                        },
                        "fair_market_value": {
                          "type": "number",
                          "exclusiveMinimum": 0,
                          "description": "Fair market value at the end of the identification period, USD."
                        },
                        "identified_date": {
                          "description": "Date the written identification was delivered, YYYY-MM-DD. Optional; checked against the 45-day deadline.",
                          "type": "string",
                          "format": "date",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                        },
                        "received": {
                          "default": false,
                          "description": "Whether the taxpayer acquired this property.",
                          "type": "boolean"
                        },
                        "received_date": {
                          "description": "Date it was acquired, YYYY-MM-DD. Optional; checked against the exchange deadline.",
                          "type": "string",
                          "format": "date",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                        }
                      },
                      "required": [
                        "name",
                        "fair_market_value"
                      ]
                    }
                  }
                },
                "required": [
                  "relinquished_transfer_date",
                  "relinquished"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/critical_dates": {
      "post": {
        "operationId": "calc_critical_dates",
        "summary": "Lease critical dates",
        "description": "Dated list of a lease's notice deadlines and option windows, from its expiration date and options: each renewal option's notice deadline or notice window and renewal term (successive options count back from the term before), each termination option's last day to give notice, an expansion option deadline, and other deadlines (a stated date, or N days after a date such as a ROFO notice). Calendar months; N months before a date keeps the day of month, clamped to a shorter month's end (a warning gives the month-end reading when it is later). Deadlines on a weekend or US federal holiday (5 U.S.C. §6103, observed days included) are flagged, or rolled to the preceding or following business day when business_day_roll says so. With as_of: days to each event, passed deadlines warned, and the next deadline. Steps show each computation. Deterministic: use this instead of doing the math yourself; cite formula calc.critical_dates@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "expiration_date": {
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                    "description": "Expiration date of the current term (YYYY-MM-DD), as the lease or the latest amendment states it."
                  },
                  "commencement_date": {
                    "description": "Commencement date (YYYY-MM-DD). Optional; listed as an event and checked to be before expiration_date.",
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                  },
                  "as_of": {
                    "description": "Date to measure from (YYYY-MM-DD), usually today. Deadlines before it are flagged as passed; next_deadline is the first one on or after it.",
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                  },
                  "renewal_option": {
                    "description": "Renewal options, as lease.renewal_option states them: each is exercised by notice counted back from the expiration in effect when it is exercised.",
                    "type": "object",
                    "properties": {
                      "count": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 10,
                        "description": "Number of successive renewal options."
                      },
                      "term_months": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 600,
                        "description": "Length of each renewal term in calendar months."
                      },
                      "notice_months": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 120,
                        "description": "Notice \"no later than N months before\" the date it counts back from, in calendar months."
                      },
                      "notice_days": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 3650,
                        "description": "Notice \"no later than N days before\" the date it counts back from, in calendar days."
                      },
                      "notice_window": {
                        "description": "A notice window (\"not earlier than 18 nor later than 12 months before expiration\"), instead of notice_months or notice_days.",
                        "type": "object",
                        "properties": {
                          "earliest_months_before": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 120,
                            "description": "The window opens this many calendar months before the expiration in effect (notice given earlier is premature)."
                          },
                          "latest_months_before": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 120,
                            "description": "The window closes this many calendar months before the expiration in effect (the last day to give notice)."
                          }
                        },
                        "required": [
                          "earliest_months_before",
                          "latest_months_before"
                        ]
                      }
                    },
                    "required": [
                      "count",
                      "term_months"
                    ]
                  },
                  "termination_options": {
                    "default": [],
                    "description": "Early termination options, as lease.termination_option states them; notice counts back from each effective_date.",
                    "maxItems": 20,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "effective_date": {
                          "type": "string",
                          "format": "date",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                          "description": "Date the early termination takes effect (YYYY-MM-DD)."
                        },
                        "notice_months": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 120,
                          "description": "Notice \"no later than N months before\" the date it counts back from, in calendar months."
                        },
                        "notice_days": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 3650,
                          "description": "Notice \"no later than N days before\" the date it counts back from, in calendar days."
                        },
                        "label": {
                          "description": "Name for this option, e.g. \"Year 5 termination right\".",
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200
                        }
                      },
                      "required": [
                        "effective_date"
                      ]
                    }
                  },
                  "expansion_option_deadline": {
                    "description": "Last day to exercise an expansion option (lease.expansion_option_deadline), YYYY-MM-DD.",
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                  },
                  "other_deadlines": {
                    "default": [],
                    "description": "Other deadlines: expansion rights, ROFO or ROFR response periods, contraction rights.",
                    "maxItems": 50,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "label": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200,
                          "description": "What the deadline is, e.g. \"ROFO response for Suite 410\"."
                        },
                        "date": {
                          "description": "The deadline, when the lease states the date.",
                          "type": "string",
                          "format": "date",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                        },
                        "from_date": {
                          "description": "Or the date it counts from (e.g. the day Landlord's ROFO notice was received), with days_after.",
                          "type": "string",
                          "format": "date",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                        },
                        "days_after": {
                          "description": "Calendar days after from_date.",
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 3650
                        }
                      },
                      "required": [
                        "label"
                      ]
                    }
                  },
                  "business_day_roll": {
                    "default": "none",
                    "description": "What to do with a deadline that falls on a weekend or holiday: \"none\" (default) keeps the date and flags it; \"preceding\" moves it to the business day before (never later than the lease date: the safe choice); \"following\" moves it to the next business day, which only a lease clause that says so allows.",
                    "type": "string",
                    "enum": [
                      "none",
                      "preceding",
                      "following"
                    ]
                  },
                  "holidays": {
                    "default": "us_federal",
                    "description": "Holidays that are not business days: \"us_federal\" (the eleven US federal holidays of 5 U.S.C. §6103(a), with the federal observed day when one falls on a weekend) or \"none\" (weekends only).",
                    "type": "string",
                    "enum": [
                      "us_federal",
                      "none"
                    ]
                  }
                },
                "required": [
                  "expiration_date"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/reconcile_dcf": {
      "post": {
        "operationId": "calc_reconcile_dcf",
        "summary": "Tie out a DCF against another system's annual lines",
        "description": "Reconcile FactLot's DCF with another system's export (ARGUS or any other model; parity is not claimed and is untested against real ARGUS output): give the calc_dcf_pro_forma input plus the other system's annual lines {label, values: [year 1, …]}, optionally its value, unlevered IRR and sale price, a tolerance (absolute and relative) and your label mapping. Labels map to FactLot lines by your mapping, else by common CRE line names (Potential Base Rent, Absorption & Turnover Vacancy, Free Rent, Expense Reimbursements, General Vacancy, Credit Loss, Effective Gross Revenue, Operating Expenses, Net Operating Income, Tenant Improvements, Leasing Commissions, Capital Reserves, Cash Flow Before Debt Service); positive deductions are negated (sign_convention). Returns per line and year FactLot's amount, the other amount, the difference and whether it is within tolerance; unmapped labels; and for each differing line a likely_cause from FactLot's documented conventions, worded as a hypothesis to check: differences only in rollover years (fractional downtime and free rent), a rate on a different base (general vacancy), only the exit year, a one-year shift, management fee inside operating expenses, a growth rate, a constant factor or amount, rounding; and for the value FactLot rerun with mid-year or end-of-year discounting, the other exit NOI basis, or no selling costs. Deterministic: use this instead of doing the math yourself; cite formula calc.reconcile_dcf@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "dcf": {
                    "type": "object",
                    "properties": {
                      "hold_years": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 30,
                        "description": "Holding period in years. The property is sold at the end of the last year; one more year is projected for the forward NOI."
                      },
                      "leases": {
                        "minItems": 1,
                        "maxItems": 200,
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200,
                              "description": "Suite or tenant label, e.g. \"Suite 200\"."
                            },
                            "area_sf": {
                              "type": "number",
                              "exclusiveMinimum": 0,
                              "description": "Leased area, SF."
                            },
                            "rent_psf_annual": {
                              "type": "number",
                              "minimum": 0,
                              "description": "Current in-place base rent, USD/SF/yr (0 for space vacant at the start)."
                            },
                            "months_remaining": {
                              "type": "integer",
                              "minimum": 0,
                              "maximum": 600,
                              "description": "Months left on the lease from the analysis start: the lease runs through this month (1 = expires at the end of month 1). 0 means the space is vacant at the start and is leased to a new tenant after downtime_months."
                            },
                            "escalation_rate": {
                              "default": 0,
                              "description": "Annual bump in the in-place rent, e.g. 0.03, applied first in next_escalation_month and every 12 months after.",
                              "type": "number",
                              "minimum": 0,
                              "exclusiveMaximum": 1
                            },
                            "next_escalation_month": {
                              "default": 13,
                              "description": "Analysis month of the next in-place rent bump (default 13, the start of year 2). Use the lease's anniversary month.",
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 600
                            },
                            "free_rent_months_remaining": {
                              "default": 0,
                              "description": "Months of in-place free base rent still to run from the analysis start (months 1 to this are abated).",
                              "type": "integer",
                              "minimum": 0,
                              "maximum": 600
                            },
                            "recovery": {
                              "default": "nnn",
                              "description": "In-place expense recovery: \"nnn\", \"gross\", or \"base_year\" (needs base_year_opex_psf_annual).",
                              "type": "string",
                              "enum": [
                                "nnn",
                                "gross",
                                "base_year"
                              ]
                            },
                            "base_year_opex_psf_annual": {
                              "description": "Operating expenses in the lease's base year, USD/SF/yr: the tenant pays the excess above it. Required when recovery is base_year.",
                              "type": "number",
                              "minimum": 0
                            },
                            "market_rent_psf_annual": {
                              "type": "number",
                              "minimum": 0,
                              "description": "Market rent for this space today, USD/SF/yr. Grows at market_rent_growth_rate each analysis year."
                            },
                            "leasing": {
                              "type": "object",
                              "properties": {
                                "renewal_probability": {
                                  "type": "number",
                                  "minimum": 0,
                                  "maximum": 1,
                                  "description": "Probability the tenant renews at expiration, 0 to 1 (e.g. 0.65). Blends the renewal and new-tenant assumptions below."
                                },
                                "lease_term_months": {
                                  "type": "integer",
                                  "minimum": 1,
                                  "maximum": 240,
                                  "description": "Term of each lease signed at rollover (renewal or new tenant), months."
                                },
                                "downtime_months": {
                                  "type": "integer",
                                  "minimum": 0,
                                  "maximum": 60,
                                  "description": "Months vacant before a new tenant starts paying (no downtime is assumed on renewal). Blended downtime = (1 − renewal_probability) × this, kept fractional, not rounded: the suite is vacant for the whole months and the rollover lease's first month collects 1 − the fraction of its rent and recoveries (0.45 months of downtime: the first month collects 55%)."
                                },
                                "renewal_rent_pct_of_market": {
                                  "type": "number",
                                  "exclusiveMinimum": 0,
                                  "maximum": 2,
                                  "description": "Renewal rent as a share of market rent, e.g. 0.95 for a 5% renewal discount; new tenants pay market rent."
                                },
                                "rent_escalation_rate": {
                                  "type": "number",
                                  "minimum": 0,
                                  "exclusiveMaximum": 1,
                                  "description": "Annual bump in a rollover lease's base rent, compounding every 12 months from its start, e.g. 0.03."
                                },
                                "free_rent_months_new": {
                                  "type": "integer",
                                  "minimum": 0,
                                  "maximum": 60,
                                  "description": "Months of free base rent for a new tenant at the start of its lease."
                                },
                                "free_rent_months_renewal": {
                                  "type": "integer",
                                  "minimum": 0,
                                  "maximum": 60,
                                  "description": "Months of free base rent on a renewal. Blended free rent = p × this + (1 − p) × free_rent_months_new, kept fractional: the whole months are fully abated and the next month by the fraction."
                                },
                                "ti_psf_new": {
                                  "type": "number",
                                  "minimum": 0,
                                  "description": "Tenant improvement allowance for a new tenant, USD/SF in today's dollars (grown at market_rent_growth_rate to the lease start)."
                                },
                                "ti_psf_renewal": {
                                  "type": "number",
                                  "minimum": 0,
                                  "description": "Tenant improvement allowance on a renewal, USD/SF in today's dollars."
                                },
                                "lc_rate_new": {
                                  "type": "number",
                                  "minimum": 0,
                                  "exclusiveMaximum": 1,
                                  "description": "Leasing commission for a new tenant as a share of the base rent payable over the lease term after free rent, e.g. 0.06."
                                },
                                "lc_rate_renewal": {
                                  "type": "number",
                                  "minimum": 0,
                                  "exclusiveMaximum": 1,
                                  "description": "Leasing commission on a renewal, same basis, e.g. 0.03."
                                },
                                "recovery": {
                                  "type": "string",
                                  "enum": [
                                    "nnn",
                                    "gross",
                                    "base_year"
                                  ],
                                  "description": "Expense recovery on rollover leases: \"nnn\" (tenant pays its share of operating expenses), \"gross\" (none), or \"base_year\" (expenses above the lease's first-year level)."
                                }
                              },
                              "description": "Per-lease overrides of market_leasing (any subset of its fields)."
                            }
                          },
                          "required": [
                            "name",
                            "area_sf",
                            "rent_psf_annual",
                            "months_remaining",
                            "market_rent_psf_annual"
                          ]
                        },
                        "description": "Rent roll: one entry per lease or vacant suite (at most 200). The building area is the sum of their areas."
                      },
                      "market_leasing": {
                        "type": "object",
                        "properties": {
                          "renewal_probability": {
                            "type": "number",
                            "minimum": 0,
                            "maximum": 1,
                            "description": "Probability the tenant renews at expiration, 0 to 1 (e.g. 0.65). Blends the renewal and new-tenant assumptions below."
                          },
                          "lease_term_months": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 240,
                            "description": "Term of each lease signed at rollover (renewal or new tenant), months."
                          },
                          "downtime_months": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 60,
                            "description": "Months vacant before a new tenant starts paying (no downtime is assumed on renewal). Blended downtime = (1 − renewal_probability) × this, kept fractional, not rounded: the suite is vacant for the whole months and the rollover lease's first month collects 1 − the fraction of its rent and recoveries (0.45 months of downtime: the first month collects 55%)."
                          },
                          "renewal_rent_pct_of_market": {
                            "default": 1,
                            "type": "number",
                            "exclusiveMinimum": 0,
                            "maximum": 2,
                            "description": "Renewal rent as a share of market rent, e.g. 0.95 for a 5% renewal discount; new tenants pay market rent."
                          },
                          "rent_escalation_rate": {
                            "default": 0,
                            "type": "number",
                            "minimum": 0,
                            "exclusiveMaximum": 1,
                            "description": "Annual bump in a rollover lease's base rent, compounding every 12 months from its start, e.g. 0.03."
                          },
                          "free_rent_months_new": {
                            "default": 0,
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 60,
                            "description": "Months of free base rent for a new tenant at the start of its lease."
                          },
                          "free_rent_months_renewal": {
                            "default": 0,
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 60,
                            "description": "Months of free base rent on a renewal. Blended free rent = p × this + (1 − p) × free_rent_months_new, kept fractional: the whole months are fully abated and the next month by the fraction."
                          },
                          "ti_psf_new": {
                            "default": 0,
                            "type": "number",
                            "minimum": 0,
                            "description": "Tenant improvement allowance for a new tenant, USD/SF in today's dollars (grown at market_rent_growth_rate to the lease start)."
                          },
                          "ti_psf_renewal": {
                            "default": 0,
                            "type": "number",
                            "minimum": 0,
                            "description": "Tenant improvement allowance on a renewal, USD/SF in today's dollars."
                          },
                          "lc_rate_new": {
                            "default": 0,
                            "type": "number",
                            "minimum": 0,
                            "exclusiveMaximum": 1,
                            "description": "Leasing commission for a new tenant as a share of the base rent payable over the lease term after free rent, e.g. 0.06."
                          },
                          "lc_rate_renewal": {
                            "default": 0,
                            "type": "number",
                            "minimum": 0,
                            "exclusiveMaximum": 1,
                            "description": "Leasing commission on a renewal, same basis, e.g. 0.03."
                          },
                          "recovery": {
                            "default": "nnn",
                            "type": "string",
                            "enum": [
                              "nnn",
                              "gross",
                              "base_year"
                            ],
                            "description": "Expense recovery on rollover leases: \"nnn\" (tenant pays its share of operating expenses), \"gross\" (none), or \"base_year\" (expenses above the lease's first-year level)."
                          }
                        },
                        "required": [
                          "renewal_probability",
                          "lease_term_months",
                          "downtime_months"
                        ],
                        "description": "Market leasing assumptions applied at every lease expiration (and to space vacant at the start). A lease's own `leasing` object overrides any of them."
                      },
                      "market_rent_growth_rate": {
                        "default": 0,
                        "description": "Annual market rent growth, e.g. 0.03. Market rent and TI in analysis year y = today's × (1 + g)^(y − 1).",
                        "type": "number",
                        "exclusiveMinimum": -1,
                        "exclusiveMaximum": 1
                      },
                      "operating_expenses_psf_annual": {
                        "default": 0,
                        "description": "Year-1 recoverable operating expenses, USD/SF/yr over the whole building area.",
                        "type": "number",
                        "minimum": 0
                      },
                      "non_recoverable_expenses_annual": {
                        "default": 0,
                        "description": "Year-1 operating expenses no tenant reimburses, USD/yr.",
                        "type": "number",
                        "minimum": 0
                      },
                      "expense_growth_rate": {
                        "default": 0,
                        "description": "Annual growth of operating expenses and capital reserves, e.g. 0.03: year y = year 1 × (1 + g)^(y − 1).",
                        "type": "number",
                        "exclusiveMinimum": -1,
                        "exclusiveMaximum": 1
                      },
                      "management_fee_rate": {
                        "default": 0,
                        "description": "Management fee as a share of effective gross revenue, e.g. 0.03; not recovered.",
                        "type": "number",
                        "minimum": 0,
                        "exclusiveMaximum": 1
                      },
                      "general_vacancy_rate": {
                        "default": 0,
                        "description": "General vacancy allowance as a share of gross revenue (base rent after free rent, plus recoveries), on top of the downtime modelled at rollover.",
                        "type": "number",
                        "minimum": 0,
                        "exclusiveMaximum": 1
                      },
                      "credit_loss_rate": {
                        "default": 0,
                        "description": "Credit loss as a share of gross revenue, on top of general vacancy.",
                        "type": "number",
                        "minimum": 0,
                        "exclusiveMaximum": 1
                      },
                      "capital_reserve_psf_annual": {
                        "default": 0,
                        "description": "Year-1 capital reserve, USD/SF/yr over the building area, below NOI. Grows with expense_growth_rate.",
                        "type": "number",
                        "minimum": 0
                      },
                      "exit_cap_rate": {
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "exclusiveMaximum": 1,
                        "description": "Exit (terminal) cap rate applied to the exit NOI, e.g. 0.07."
                      },
                      "exit_noi_basis": {
                        "default": "forward",
                        "description": "\"forward\": the NOI of the year after the hold (the usual convention); \"trailing\": the NOI of the final hold year.",
                        "type": "string",
                        "enum": [
                          "forward",
                          "trailing"
                        ]
                      },
                      "selling_cost_rate": {
                        "default": 0,
                        "description": "Selling costs as a share of the gross sale price, e.g. 0.02.",
                        "type": "number",
                        "minimum": 0,
                        "exclusiveMaximum": 1
                      },
                      "discount_rate": {
                        "type": "number",
                        "minimum": 0,
                        "exclusiveMaximum": 1,
                        "description": "Annual unlevered discount rate, e.g. 0.08. The present value at this rate is the value."
                      },
                      "timing": {
                        "default": "end_of_year",
                        "description": "\"end_of_year\": each year's cash flow is discounted (1 + r)^y (Excel NPV). \"mid_year\": operating cash flows at (1 + r)^(y − 0.5); the sale is always discounted at the end of the hold.",
                        "type": "string",
                        "enum": [
                          "end_of_year",
                          "mid_year"
                        ]
                      },
                      "purchase_price": {
                        "description": "Price paid at the start, USD. Adds the NPV (value − price), the unlevered IRR and the going-in cap rate on price.",
                        "type": "number",
                        "exclusiveMinimum": 0
                      }
                    },
                    "required": [
                      "hold_years",
                      "leases",
                      "market_leasing",
                      "exit_cap_rate",
                      "discount_rate"
                    ],
                    "description": "The FactLot DCF input, exactly as calc_dcf_pro_forma takes it (the same rent roll and assumptions you entered in the other system)."
                  },
                  "other": {
                    "type": "object",
                    "properties": {
                      "system": {
                        "default": "other system",
                        "description": "Name of the system or file the lines come from, echoed in the result, e.g. \"ARGUS export, Nov 2026\" (free text).",
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200
                      },
                      "lines": {
                        "minItems": 1,
                        "maxItems": 200,
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "label": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200,
                              "description": "Line name as the other system prints it, e.g. \"Potential Base Rent\". Compared ignoring case and punctuation, \"&\" read as \"and\"."
                            },
                            "values": {
                              "minItems": 1,
                              "maxItems": 31,
                              "type": "array",
                              "items": {
                                "type": "number"
                              },
                              "description": "Annual amounts in USD, year 1 first, one per analysis year (the hold plus the year after it at most). Deductions may be negative or positive; see sign_convention."
                            }
                          },
                          "required": [
                            "label",
                            "values"
                          ]
                        },
                        "description": "The other system's annual cash-flow lines (factlot.reconcile_files reads them from a CSV or XLSX)."
                      },
                      "value": {
                        "description": "The other system's present value at the discount rate, USD.",
                        "type": "number"
                      },
                      "unlevered_irr": {
                        "description": "The other system's unlevered IRR as a decimal (0.0825 for 8.25%). Compared only when dcf.purchase_price is given.",
                        "type": "number",
                        "exclusiveMinimum": -1,
                        "maximum": 1
                      },
                      "gross_sale_price": {
                        "description": "The other system's gross sale (reversion) price before selling costs, USD.",
                        "type": "number"
                      },
                      "net_sale_proceeds": {
                        "description": "The other system's sale proceeds after selling costs, USD.",
                        "type": "number"
                      }
                    },
                    "required": [
                      "lines"
                    ],
                    "description": "What the other system reported for the same property."
                  },
                  "mapping": {
                    "default": [],
                    "description": "Your label mappings; they override the defaults (common CRE line names such as Potential Base Rent, General Vacancy, Net Operating Income, Cash Flow Before Debt Service).",
                    "maxItems": 200,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "label": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200,
                          "description": "A label in other.lines (matched ignoring case and punctuation)."
                        },
                        "line": {
                          "type": "string",
                          "enum": [
                            "potential_base_rent",
                            "free_rent",
                            "expense_recoveries",
                            "gross_revenue",
                            "general_vacancy",
                            "credit_loss",
                            "effective_gross_revenue",
                            "operating_expenses",
                            "management_fee",
                            "net_operating_income",
                            "tenant_improvements",
                            "leasing_commissions",
                            "capital_reserves",
                            "cash_flow_before_debt",
                            "turnover_vacancy",
                            "ignore"
                          ],
                          "description": "FactLot line to compare it with. \"turnover_vacancy\" is a deduction compared together with potential_base_rent (FactLot's potential base rent already excludes rollover downtime); \"ignore\" leaves the line out. Several labels mapped to one line are summed."
                        }
                      },
                      "required": [
                        "label",
                        "line"
                      ]
                    }
                  },
                  "sign_convention": {
                    "default": "auto",
                    "description": "FactLot reports deductions (free rent, vacancy, credit loss, expenses, fees, TI, commissions, reserves) as negative amounts. \"auto\": a deduction line whose amounts are all zero or positive is read as positive deductions and negated; \"as_reported\": compare as given; \"deductions_positive\": negate every deduction line.",
                    "type": "string",
                    "enum": [
                      "auto",
                      "as_reported",
                      "deductions_positive"
                    ]
                  },
                  "tolerance": {
                    "default": {
                      "absolute": 1,
                      "relative": 0,
                      "irr": 0.0001
                    },
                    "description": "When two amounts agree: |FactLot − other| ≤ max(absolute, relative × |other|); the IRR within irr. Default: 1.00 USD, 0, and one basis point.",
                    "type": "object",
                    "properties": {
                      "absolute": {
                        "default": 1,
                        "description": "Largest difference, USD, still counted as agreeing (default 1.00).",
                        "type": "number",
                        "minimum": 0
                      },
                      "relative": {
                        "default": 0,
                        "description": "Largest difference as a share of the other system's amount still counted as agreeing, e.g. 0.0001; the larger of the two tolerances applies (default 0).",
                        "type": "number",
                        "minimum": 0,
                        "exclusiveMaximum": 1
                      },
                      "irr": {
                        "default": 0.0001,
                        "description": "Largest IRR difference still counted as agreeing, as a decimal (default 0.0001, one basis point).",
                        "type": "number",
                        "minimum": 0,
                        "exclusiveMaximum": 1
                      }
                    }
                  }
                },
                "required": [
                  "dcf",
                  "other"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/reconcile_cam": {
      "post": {
        "operationId": "calc_reconcile_cam",
        "summary": "Tie out CAM reconciliations against another system's results",
        "description": "Reconcile FactLot's CAM reconciliation for one expense pool with another system's per-tenant results (Yardi or any other; parity is not claimed and is untested against a real export): give the calc_cam_reconciliation_batch input plus, per tenant, what the other system reported (share, gross-up, cap limit and reduction, admin fee, base-year or stop deduction, billed amount, estimates, balance; billed_amount required) and a tolerance. Returns per tenant and line FactLot's amount, the other amount, the difference and whether it is within tolerance, tenants missing on either side, and likely causes worded as hypotheses to check: a different pro rata share (and, when every share differs by one factor, the pool's denominator), or a FactLot rerun that matches the other system with lines left to the default controllable: true set uncontrollable, the cap's base year one year off, the other cap method, the other gross-up target or no gross-up, the admin fee before or after and within or outside the cap, or no fee on uncontrollable lines; and differing estimates paid. Deterministic: use this instead of doing the math yourself; cite formula calc.reconcile_cam@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cam": {
                    "type": "object",
                    "properties": {
                      "year": {
                        "type": "integer",
                        "minimum": 1900,
                        "maximum": 2200,
                        "description": "Calendar or lease year being reconciled."
                      },
                      "expenses": {
                        "minItems": 1,
                        "maxItems": 1000,
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "category": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200,
                              "description": "Expense line as named in the operating statement, e.g. \"janitorial\"."
                            },
                            "amount": {
                              "type": "number",
                              "minimum": 0,
                              "description": "Building-level actual expense for the year, USD."
                            },
                            "controllable": {
                              "default": true,
                              "description": "Subject to the cap. Taxes, insurance, and utilities are typically uncontrollable.",
                              "type": "boolean"
                            },
                            "recoverable": {
                              "default": true,
                              "description": "Excluded expenses (capital items, landlord-only costs) are dropped entirely.",
                              "type": "boolean"
                            },
                            "variable": {
                              "default": false,
                              "description": "Varies with occupancy, so eligible for gross-up (janitorial, utilities, management fee).",
                              "type": "boolean"
                            },
                            "admin_fee_applies": {
                              "default": true,
                              "description": "Included in the admin_fee base. Set false for lines the lease excludes from the fee (often taxes, insurance, utilities, or a management fee already charged).",
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "category",
                            "amount"
                          ]
                        },
                        "description": "The building's operating expense pool for the year, entered once for every tenant: category, amount, controllable, recoverable, variable, admin_fee_applies."
                      },
                      "gross_up": {
                        "description": "Gross up variable expenses to the target occupancy when actual occupancy is lower. Omit if the lease has no gross-up.",
                        "type": "object",
                        "properties": {
                          "actual_occupancy": {
                            "type": "number",
                            "exclusiveMinimum": 0,
                            "maximum": 1,
                            "description": "Average occupancy for the year, e.g. 0.82."
                          },
                          "target_occupancy": {
                            "default": 0.95,
                            "description": "Gross-up occupancy stated in the lease (commonly 0.95 or 1.0).",
                            "type": "number",
                            "exclusiveMinimum": 0,
                            "maximum": 1
                          }
                        },
                        "required": [
                          "actual_occupancy"
                        ]
                      },
                      "tenants": {
                        "minItems": 1,
                        "maxItems": 500,
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200,
                              "description": "Tenant or suite label, unique, e.g. \"Suite 200 – Acme\"."
                            },
                            "pro_rata_share": {
                              "type": "number",
                              "exclusiveMinimum": 0,
                              "maximum": 1,
                              "description": "Tenant's pro rata share, 0 < share ≤ 1 (e.g. 12,000 / 150,000 = 0.08)."
                            },
                            "structure": {
                              "default": {
                                "type": "net"
                              },
                              "description": "Expense structure from the lease. Default: net.",
                              "oneOf": [
                                {
                                  "type": "object",
                                  "properties": {
                                    "type": {
                                      "type": "string",
                                      "const": "net"
                                    }
                                  },
                                  "required": [
                                    "type"
                                  ],
                                  "description": "Tenant pays its full share of recoverable expenses (NNN / net)."
                                },
                                {
                                  "type": "object",
                                  "properties": {
                                    "type": {
                                      "type": "string",
                                      "const": "base_year"
                                    },
                                    "base_year_expenses": {
                                      "type": "number",
                                      "minimum": 0,
                                      "description": "Total building recoverable expenses in the base year, already grossed up, USD."
                                    }
                                  },
                                  "required": [
                                    "type",
                                    "base_year_expenses"
                                  ],
                                  "description": "Tenant pays its share of the excess over base-year expenses."
                                },
                                {
                                  "type": "object",
                                  "properties": {
                                    "type": {
                                      "type": "string",
                                      "const": "expense_stop"
                                    },
                                    "stop_amount": {
                                      "type": "number",
                                      "minimum": 0,
                                      "description": "Building-level expense stop, USD per year."
                                    }
                                  },
                                  "required": [
                                    "type",
                                    "stop_amount"
                                  ],
                                  "description": "Tenant pays its share of the excess over a fixed expense stop."
                                }
                              ]
                            },
                            "cap": {
                              "description": "Cap on the tenant's share of controllable expenses. Omit if the lease has no cap.",
                              "type": "object",
                              "properties": {
                                "method": {
                                  "type": "string",
                                  "enum": [
                                    "compounding_from_base",
                                    "simple_from_base",
                                    "year_over_year"
                                  ],
                                  "description": "compounding_from_base: reference × (1+rate)^years (often called cumulative, compounding). simple_from_base: reference × (1 + rate × years) (cumulative, non-compounding). year_over_year: prior year's capped amount × (1+rate) (non-cumulative). Map the lease language to one of these explicitly."
                                },
                                "rate": {
                                  "type": "number",
                                  "exclusiveMinimum": -1,
                                  "exclusiveMaximum": 10,
                                  "description": "Annual cap as a decimal, e.g. 0.05."
                                },
                                "reference_amount": {
                                  "type": "number",
                                  "minimum": 0,
                                  "description": "Tenant's share of controllable expenses in the reference year: the base year for *_from_base, or the prior year's capped amount for year_over_year, USD."
                                },
                                "years_elapsed": {
                                  "description": "Years since the base year (required for `*_from_base`).",
                                  "type": "integer",
                                  "minimum": 0,
                                  "maximum": 9007199254740991
                                }
                              },
                              "required": [
                                "method",
                                "rate",
                                "reference_amount"
                              ]
                            },
                            "admin_fee": {
                              "description": "Administrative fee the lease adds to the tenant's share. Omit if the lease has none.",
                              "type": "object",
                              "properties": {
                                "rate": {
                                  "type": "number",
                                  "exclusiveMinimum": -1,
                                  "exclusiveMaximum": 10,
                                  "description": "Administrative or management fee as a decimal of the fee base, e.g. 0.15 for 15%. The base is the tenant's share of recoverable lines with admin_fee_applies (after gross-up)."
                                },
                                "applied": {
                                  "default": "after_cap",
                                  "description": "Fee on controllable lines computed on the tenant's share before the cap (before_cap) or on the capped amount (after_cap). Fees on uncontrollable lines are never capped. Irrelevant without a cap.",
                                  "type": "string",
                                  "enum": [
                                    "before_cap",
                                    "after_cap"
                                  ]
                                },
                                "capped": {
                                  "default": false,
                                  "description": "True when the fee on controllable lines counts toward the controllable cap (controllable share plus fee may not exceed the cap limit); false adds it outside the cap.",
                                  "type": "boolean"
                                }
                              },
                              "required": [
                                "rate"
                              ]
                            },
                            "occupancy": {
                              "description": "Partial-year occupancy: the tenant's share, cap limit, and base-year or stop deduction are prorated by occupied days ÷ period days (both inclusive), so a full-year cap and base are not applied to a part year. Omit for a full year.",
                              "type": "object",
                              "properties": {
                                "move_in_date": {
                                  "description": "Date the tenant's share starts within the reconciliation period, YYYY-MM-DD (inclusive). Defaults to period_start_date.",
                                  "type": "string",
                                  "format": "date",
                                  "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                                },
                                "move_out_date": {
                                  "description": "Last day of the tenant's share within the period, YYYY-MM-DD (inclusive). Defaults to period_end_date.",
                                  "type": "string",
                                  "format": "date",
                                  "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                                },
                                "period_start_date": {
                                  "description": "First day of the reconciliation period, YYYY-MM-DD. Defaults to January 1 of year; set it for a lease year.",
                                  "type": "string",
                                  "format": "date",
                                  "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                                },
                                "period_end_date": {
                                  "description": "Last day of the reconciliation period, YYYY-MM-DD (at most 366 days after the start). Defaults to December 31 of year.",
                                  "type": "string",
                                  "format": "date",
                                  "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                                }
                              }
                            },
                            "estimates_paid": {
                              "default": 0,
                              "description": "Estimated CAM payments the tenant made during the year, USD (total for the year, not monthly).",
                              "type": "number",
                              "minimum": 0
                            },
                            "excluded_categories": {
                              "default": [],
                              "description": "Expense categories this tenant's lease excludes, treated as recoverable: false for this tenant only. Each must match a category in expenses.",
                              "maxItems": 1000,
                              "type": "array",
                              "items": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 200
                              }
                            },
                            "admin_fee_excluded_categories": {
                              "default": [],
                              "description": "Expense categories this tenant's lease keeps out of the admin fee base, treated as admin_fee_applies: false for this tenant only. Each must match a category in expenses.",
                              "maxItems": 1000,
                              "type": "array",
                              "items": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 200
                              }
                            }
                          },
                          "required": [
                            "name",
                            "pro_rata_share"
                          ]
                        },
                        "description": "Every tenant to reconcile (up to 500): each one's share, structure (base year or stop), cap, admin fee, occupancy, estimates paid, and lease-specific exclusions. Each statement equals calc_cam_reconciliation for that tenant."
                      }
                    },
                    "required": [
                      "year",
                      "expenses",
                      "tenants"
                    ],
                    "description": "The FactLot CAM input for one expense pool, exactly as calc_cam_reconciliation_batch takes it (the same pool, shares and lease terms you entered in the other system)."
                  },
                  "other": {
                    "type": "object",
                    "properties": {
                      "system": {
                        "default": "other system",
                        "description": "Name of the system or file the results come from, echoed in the result, e.g. \"Yardi CAM recon export\" (free text).",
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200
                      },
                      "pool": {
                        "description": "The pool these results are for, e.g. \"CAM\", echoed in the result.",
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200
                      },
                      "tenants": {
                        "minItems": 1,
                        "maxItems": 500,
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "tenant": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200,
                              "description": "Tenant as the other system names it; matched to cam.tenants[].name exactly, else ignoring case and punctuation."
                            },
                            "pool": {
                              "description": "The other system's expense pool for this row, e.g. \"CAM\" (echoed; reconcile one pool per call).",
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200
                            },
                            "pro_rata_share": {
                              "description": "The other system's pro rata share for the tenant, 0 < share ≤ 1.",
                              "type": "number",
                              "exclusiveMinimum": 0,
                              "maximum": 1
                            },
                            "gross_up_applied": {
                              "description": "Whether the other system grossed up variable expenses for this tenant.",
                              "type": "boolean"
                            },
                            "grossed_up_expenses": {
                              "description": "The other system's pool after gross-up (the building amount before the tenant's share), USD.",
                              "type": "number",
                              "minimum": 0
                            },
                            "cap_applied": {
                              "description": "Whether a cap reduced the other system's charge.",
                              "type": "boolean"
                            },
                            "cap_limit": {
                              "description": "The other system's cap limit on the tenant's controllable share, USD.",
                              "type": "number",
                              "minimum": 0
                            },
                            "cap_reduction": {
                              "description": "Amount the cap removed in the other system, USD.",
                              "type": "number",
                              "minimum": 0
                            },
                            "admin_fee_applied": {
                              "description": "Whether the other system charged an admin fee.",
                              "type": "boolean"
                            },
                            "admin_fee": {
                              "description": "The other system's admin or management fee for the tenant, USD.",
                              "type": "number",
                              "minimum": 0
                            },
                            "structure_deduction": {
                              "description": "The other system's base-year or expense-stop deduction, USD.",
                              "type": "number",
                              "minimum": 0
                            },
                            "billed_amount": {
                              "type": "number",
                              "minimum": 0,
                              "description": "What the other system billed the tenant for the year before estimates (FactLot's tenant_charge), USD."
                            },
                            "estimates_paid": {
                              "description": "Estimates (prior billings) the other system credits for the year, USD.",
                              "type": "number",
                              "minimum": 0
                            },
                            "balance_due": {
                              "description": "The other system's balance due (negative for a credit), USD.",
                              "type": "number"
                            }
                          },
                          "required": [
                            "tenant",
                            "billed_amount"
                          ]
                        },
                        "description": "The other system's per-tenant results for this pool. Give whatever it reports; billed_amount is required."
                      }
                    },
                    "required": [
                      "tenants"
                    ],
                    "description": "What the other system reported for the same pool and year."
                  },
                  "tolerance": {
                    "default": {
                      "absolute": 1,
                      "relative": 0,
                      "share": 0.000001
                    },
                    "description": "When two amounts agree: |FactLot − other| ≤ max(absolute, relative × |other|); shares within share. Default: 1.00 USD, 0, and 0.000001.",
                    "type": "object",
                    "properties": {
                      "absolute": {
                        "default": 1,
                        "description": "Largest difference, USD, still counted as agreeing (default 1.00).",
                        "type": "number",
                        "minimum": 0
                      },
                      "relative": {
                        "default": 0,
                        "description": "Largest difference as a share of the other system's amount still counted as agreeing, e.g. 0.0001; the larger of the two tolerances applies (default 0).",
                        "type": "number",
                        "minimum": 0,
                        "exclusiveMaximum": 1
                      },
                      "share": {
                        "default": 0.000001,
                        "description": "Largest pro rata share difference still counted as agreeing (default 0.000001).",
                        "type": "number",
                        "minimum": 0,
                        "exclusiveMaximum": 1
                      }
                    }
                  }
                },
                "required": [
                  "cam",
                  "other"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/calc/tx_unequal_appraisal": {
      "post": {
        "operationId": "calc_tx_unequal_appraisal",
        "summary": "Texas unequal appraisal: median of adjusted comparables",
        "description": "Evidence calculation only: not an appraisal or an opinion of value, not legal or tax advice, and not a substitute for the advice of an attorney. The arithmetic of Tex. Tax Code §41.43(b)(3) and §42.26(a)(3), the subject's value against the median appraised value of a reasonable number of comparable properties appropriately adjusted. Takes 3–200 comparables chosen by the caller, each with its roll value and the caller's adjustments (percent, USD amount, or $/SF, each with a reason); optionally normalizes each comparable to value per SF of improvement or land area times the subject's area; applies percentages summed or compounded, then amounts; and returns every comparable's normalized and adjusted value, the sorted list, the median (for an even count, the mean of the two middle values), and the subject's value minus the median in USD and percent, with the statutes cited. Warns on mixed value bases, §42.26(d) and the §23.231 limitation, comparables in another county, fewer than 5 comparables, total adjustments over 25%, and a comparable identical to the subject. It does not choose comparables, invent adjustments, or say whether to protest. Deterministic: use this instead of doing the math yourself; cite formula calc.tx_unequal_appraisal@1 and the returned steps, and surface any warnings.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tax_year": {
                    "type": "integer",
                    "minimum": 1982,
                    "maximum": 2100,
                    "description": "Tax year of the values compared, e.g. 2027. Decides which text of §42.26(d) applies."
                  },
                  "subject": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "description": "Identifier of the subject property, e.g. its FactLot parcel ID.",
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200
                      },
                      "county_fips": {
                        "type": "string",
                        "pattern": "^[0-9]{3}$",
                        "description": "Texas county FIPS code, 3 digits, e.g. \"201\" for Harris. Optional; used only to warn when a comparable is in a different county (appraisal district) from the subject."
                      },
                      "value": {
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "description": "The subject's value from the appraisal roll (or the value at issue), USD."
                      },
                      "value_basis": {
                        "type": "string",
                        "enum": [
                          "appraised",
                          "market"
                        ],
                        "description": "Which roll value this is: \"appraised\" (after any limitation) or \"market\". Use the same basis for the subject and every comparable."
                      },
                      "improvement_area_sf": {
                        "description": "Improvement (building) area, SF. Required when normalize_by is improvement_area_sf.",
                        "type": "number",
                        "exclusiveMinimum": 0
                      },
                      "land_area_sf": {
                        "description": "Land area, SF. Required when normalize_by is land_area_sf.",
                        "type": "number",
                        "exclusiveMinimum": 0
                      },
                      "circuit_breaker_23_231": {
                        "default": false,
                        "description": "Whether the subject is subject to the §23.231 limitation on appraised value (the circuit breaker for non-homestead property). Only used for warnings about which value §42.26(d) compares.",
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "value",
                      "value_basis"
                    ],
                    "additionalProperties": false,
                    "description": "The property whose value is compared."
                  },
                  "normalize_by": {
                    "default": "none",
                    "description": "\"none\" compares whole values. \"improvement_area_sf\" or \"land_area_sf\" converts each comparable to value per SF of that area and multiplies by the subject's same area before adjusting; the subject and every comparable must then have that area.",
                    "type": "string",
                    "enum": [
                      "none",
                      "improvement_area_sf",
                      "land_area_sf"
                    ]
                  },
                  "adjustment_combine": {
                    "default": "sum",
                    "description": "\"sum\" adds a comparable's percentage adjustments and applies the total once (the usual adjustment grid); \"compound\" multiplies (1 + each percentage).",
                    "type": "string",
                    "enum": [
                      "sum",
                      "compound"
                    ]
                  },
                  "comparables": {
                    "minItems": 3,
                    "maxItems": 200,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "description": "Identifier of the comparable, e.g. its FactLot parcel ID or account number. Must differ from the subject's and from every other comparable's.",
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200
                        },
                        "county_fips": {
                          "type": "string",
                          "pattern": "^[0-9]{3}$",
                          "description": "Texas county FIPS code, 3 digits, e.g. \"201\" for Harris. Optional; used only to warn when a comparable is in a different county (appraisal district) from the subject."
                        },
                        "value": {
                          "type": "number",
                          "exclusiveMinimum": 0,
                          "description": "The comparable's value from the appraisal roll, USD."
                        },
                        "value_basis": {
                          "type": "string",
                          "enum": [
                            "appraised",
                            "market"
                          ],
                          "description": "Which roll value this is: \"appraised\" (after any limitation) or \"market\". Use the same basis for the subject and every comparable."
                        },
                        "improvement_area_sf": {
                          "description": "Improvement (building) area, SF. Required when normalize_by is improvement_area_sf.",
                          "type": "number",
                          "exclusiveMinimum": 0
                        },
                        "land_area_sf": {
                          "description": "Land area, SF. Required when normalize_by is land_area_sf.",
                          "type": "number",
                          "exclusiveMinimum": 0
                        },
                        "citation": {
                          "type": "object",
                          "properties": {
                            "fact_id": {
                              "description": "FactLot fact ID of the comparable's value, if it came from the registry.",
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200
                            },
                            "document_id": {
                              "description": "FactLot document ID of the source (e.g. the appraisal roll file).",
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200
                            },
                            "locator": {
                              "description": "Where in the source the value is: row, account number, page.",
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 500
                            },
                            "as_of": {
                              "description": "Date the value was read or certified, YYYY-MM-DD.",
                              "type": "string",
                              "format": "date",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                            }
                          },
                          "additionalProperties": false,
                          "description": "Where the comparable's value comes from. Passed through to the output unchanged."
                        },
                        "adjustments": {
                          "default": [],
                          "description": "Adjustments to this comparable, each with a reason. Percentages are applied first (summed or compounded per adjustment_combine), then amounts and $/SF adjustments.",
                          "maxItems": 20,
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "label": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 100,
                                "description": "What the adjustment is for, e.g. \"age\", \"size\", \"condition\"."
                              },
                              "kind": {
                                "type": "string",
                                "enum": [
                                  "percent",
                                  "amount",
                                  "psf"
                                ],
                                "description": "\"percent\": a decimal share of the normalized value (0.05 for +5%, -0.02 for -2%); \"amount\": USD added to the value (negative to deduct); \"psf\": USD per SF of the normalizing area, multiplied by the subject's area (only with normalize_by other than \"none\")."
                              },
                              "value": {
                                "type": "number",
                                "description": "The adjustment: a decimal between -1 and 1 for percent (0.05, not 5), else USD or USD/SF. Positive raises the comparable's value."
                              },
                              "reason": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 300,
                                "description": "Why the adjustment is made, in the caller's words (e.g. \"comp built 1998, subject 2006\"). Required: FactLot never invents an adjustment."
                              }
                            },
                            "required": [
                              "label",
                              "kind",
                              "value",
                              "reason"
                            ],
                            "additionalProperties": false
                          }
                        }
                      },
                      "required": [
                        "value",
                        "value_basis"
                      ],
                      "additionalProperties": false
                    },
                    "description": "The comparable properties, 3 to 200, chosen by the caller. Values must be on the same basis as the subject's."
                  }
                },
                "required": [
                  "tax_year",
                  "subject",
                  "comparables"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/rules/tx_property_tax_protest_deadline": {
      "post": {
        "operationId": "rules_tx_property_tax_protest_deadline",
        "summary": "Texas property tax protest deadline",
        "description": "Deadline to file a Texas property tax protest with the appraisal review board: the later of May 15 or 30 days after the notice of appraised value was delivered, rolled past weekends and Texas legal holidays, plus a conservative file-by date. Returns the statute citations and the rule's verification status. Verification status: verified. Cite rule rules.tx.property_tax_protest_deadline@1 and its statutes.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tax_year": {
                    "type": "integer",
                    "minimum": 2000,
                    "maximum": 2100,
                    "description": "Tax year being protested, e.g. 2026."
                  },
                  "notice_delivered_date": {
                    "description": "Date the appraisal district's notice of appraised value was delivered (Tax Code §25.19), YYYY-MM-DD. A mailed notice is presumed delivered on the day it was deposited in the mail (§1.07(c)). Omit if unknown (May 15 is then the only basis).",
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                  }
                },
                "required": [
                  "tax_year"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: { value, formula, steps, warnings, provenance }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/id/parcel": {
      "post": {
        "operationId": "id_parcel",
        "summary": "FactLot parcel ID",
        "description": "Deterministic FactLot ID for a parcel from state FIPS, county FIPS, and the assessor's parcel number (APN). The same parcel always yields the same ID, offline. Use it as the join key across systems. APNs that differ only in where a separator falls (\"12-34\", \"1-234\") get different IDs; in a county with a documented fixed-width format the variants of one APN converge (NYC BBL \"3-1234-56\" = \"3012340056\").",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "state_fips": {
                    "type": "string",
                    "maxLength": 16,
                    "description": "2-digit state FIPS code, e.g. \"06\" for California."
                  },
                  "county_fips": {
                    "type": "string",
                    "maxLength": 16,
                    "description": "3-digit county FIPS code, e.g. \"037\" for Los Angeles County."
                  },
                  "apn": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Assessor's parcel number exactly as the county prints it. Letter case and the separator characters are normalized; where a separator falls is kept, except in counties with a documented fixed-width format (NYC BBL, Los Angeles AIN, Cook County PIN, DC SSL)."
                  }
                },
                "required": [
                  "state_fips",
                  "county_fips",
                  "apn"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/id/entity": {
      "post": {
        "operationId": "id_entity",
        "summary": "FactLot legal-entity ID",
        "description": "Deterministic FactLot ID for a legal entity (LLC, LP, corporation, trust) from its registry jurisdiction and file number, or from its LEI (an LEI whose ISO 17442 check digits do not match is rejected).",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "jurisdiction": {
                    "type": "string",
                    "maxLength": 64,
                    "description": "Registry jurisdiction, e.g. \"us-de\" for Delaware, or \"lei\" for a Legal Entity Identifier."
                  },
                  "registry_number": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "File number issued by that registry, or the 20-character LEI (its ISO 17442 check digits are verified)."
                  }
                },
                "required": [
                  "jurisdiction",
                  "registry_number"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/id/space": {
      "post": {
        "operationId": "id_space",
        "summary": "FactLot space (suite) ID",
        "description": "FactLot ID for a suite or unit within a building, given the building's FactLot URN (from id_building). Suite designators are normalized: \"Suite 100\", \"Ste 100\" and \"#100\" agree, while \"1-20\" and \"120\" stay distinct.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "building_urn": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "FactLot building URN from id_building, e.g. fl:building:us-11001:20002:208-massachusetts-ave-ne (a minted fl:building:<26-character token> also works)."
                  },
                  "suite": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Suite or unit as written (\"Suite 400\", \"STE 400\", \"#400\", \"3rd Floor\" all normalize)."
                  }
                },
                "required": [
                  "building_urn",
                  "suite"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/id/building": {
      "post": {
        "operationId": "id_building",
        "summary": "FactLot building ID",
        "description": "Deterministic FactLot ID for a building from its street address: county FIPS, ZIP code, and street line. Every condominium unit, suite, and floor at the address gets the same building ID, offline. Pass it to id_space for a suite, or to registry_get to see the building's parcels.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "state_fips": {
                    "type": "string",
                    "maxLength": 16,
                    "description": "2-digit state FIPS code, e.g. \"11\" for the District of Columbia."
                  },
                  "county_fips": {
                    "type": "string",
                    "maxLength": 16,
                    "description": "3-digit county FIPS code, e.g. \"001\"."
                  },
                  "zip": {
                    "type": "string",
                    "maxLength": 16,
                    "description": "5-digit ZIP code of the address, e.g. \"20002\" (ZIP+4 accepted)."
                  },
                  "street": {
                    "type": "string",
                    "maxLength": 300,
                    "description": "Street line: house number and street, e.g. \"208 Massachusetts Ave NE\". A suite, unit or floor is ignored, and so is anything after a comma (city, state)."
                  }
                },
                "required": [
                  "state_fips",
                  "county_fips",
                  "zip",
                  "street"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/id/document": {
      "post": {
        "operationId": "id_document",
        "summary": "FactLot document ID",
        "description": "Content-addressed FactLot ID (SHA-256) for a document such as a lease, amendment, or estoppel.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content_base64": {
                    "description": "Base64 of the exact file bytes (preferred: every party gets the same ID).",
                    "type": "string"
                  },
                  "text": {
                    "description": "Document text, UTF-8 encoded before hashing. Use only when bytes are unavailable.",
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/id/parse": {
      "post": {
        "operationId": "id_parse",
        "summary": "Parse a FactLot ID",
        "description": "Validate a FactLot URN and return its kind, components, and short ID.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "urn": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "A FactLot URN to validate, e.g. fl:parcel:us-06037:5432001012."
                  }
                },
                "required": [
                  "urn"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/facts/validate": {
      "post": {
        "operationId": "fact_validate",
        "summary": "Validate a FactLot fact",
        "description": "Check one FactLot fact against the published fact schema (spec/schemas/fact.json): subject URN, dotted predicate, as_of date, provenance, optional rights; unknown fields are rejected at every level. Rules: every fact except a computed one cites at least one source (an inferred fact cites the evidence it was inferred from); a computed fact names its formula or cites a source; valid_from is not after valid_to; a source span does not end before it starts. For Ingest predicates (lease.expiration_date, lease.rentable_area_sf, lease.base_rent_schedule, ...) the value must have the predicate's shape and every source quote must state the value, by the same written-form rules as doc_verify_facts (a quote saying \"2,500 square feet\" does not support 1000). Not checked: that the quote is in the document, that the document or sha256 exists, that supersedes IDs exist, and quotes for other predicates; doc_verify_facts with the document's pages checks the quote. Returns { valid, issues }. Call before storing or sharing facts.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fact": {
                    "type": "object",
                    "propertyNames": {
                      "type": "string"
                    },
                    "additionalProperties": {},
                    "description": "A FactLot fact: subject, predicate, value, as_of, provenance (with sources), optional rights."
                  }
                },
                "required": [
                  "fact"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/doc/verify_facts": {
      "post": {
        "operationId": "doc_verify_facts",
        "summary": "Verify extracted document facts",
        "description": "Check values YOU extracted from a document against its text, deterministically; FactLot runs no model. Send the pages as text and one candidate per value: {predicate, value, quote, page}. Each candidate is checked in order: (1) the value's shape (Ingest predicates such as lease.expiration_date YYYY-MM-DD, lease.pro_rata_share in (0, 1], ordered lease.base_rent_schedule steps; any other dotted predicate needs a non-empty value); (2) the quote, at least 12 characters, is on the cited page, where only whitespace, line breaks, letter case and typographic quotes and dashes may differ (a word hyphenated across a line must keep its hyphen; a quote found on exactly one other page is accepted with the page corrected); (3) every number, date and party name in the value is written in the quote, in any common form: 1250000 matches \"$1,250,000.00\", 0.03 matches \"3%\" or \"three percent\", 2031-06-30 matches \"June 30, 2031\" or \"6/30/2031\"; text the lease states as written (a prohibited use, a carve-out, a protected use) is written in it as words, ignoring case, punctuation and line breaks; (4) a confidence you give below 0.8 holds it for review; (5) every other part of the value must be confirmed by the quote too: a true/false value, an enum or a summary not written in the quote holds the candidate for review, named in the reason. Verified means every part of the value was found in the quote. Returns verified (FactLot facts: method extracted, citing document_id, page, quote and character span, plus which value parts were checked and as what), needs_review (the quote is on the page but does not state the value: check consistency; low confidence: confidence; a part FactLot cannot confirm from the quote, such as a boolean: coverage) and rejected (wrong shape; quote missing, too short, or on several pages), each with a reason to act on. Not checked: meaning (a quote stating the number in another context passes), and whether the pages came from the file whose sha256 you give.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "document": {
                    "type": "object",
                    "properties": {
                      "pages": {
                        "minItems": 1,
                        "maxItems": 2000,
                        "type": "array",
                        "items": {
                          "type": "string",
                          "maxLength": 100000,
                          "description": "The text of one page, exactly as extracted."
                        },
                        "description": "The document's text, one string per page in order: pages[0] is page 1. Send the text as extracted (from the PDF text layer or OCR), not a summary; quotes are matched against it."
                      },
                      "sha256": {
                        "type": "string",
                        "pattern": "^[a-f0-9]{64}$",
                        "description": "Optional SHA-256 (lowercase hex) of the original file. When given, document_id is fl:document:sha256-<this>, the same ID id_document gives the file; FactLot cannot check that the pages came from that file. Without it, document_id is the SHA-256 of the pages joined by form feeds (\\f)."
                      },
                      "title": {
                        "type": "string",
                        "maxLength": 500,
                        "description": "Document title for citations, e.g. \"First Amendment to Lease\"."
                      },
                      "ocr_pages": {
                        "maxItems": 2000,
                        "type": "array",
                        "items": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 100000
                        },
                        "description": "Optional: pages (1-based) whose text came from OCR rather than the file's text layer (doc_read_pdf and factlot.read_pdf report them). Facts and citations on these pages carry ocr: true, so a reviewer checks the scan."
                      }
                    },
                    "required": [
                      "pages"
                    ],
                    "description": "The document the candidates were read from."
                  },
                  "candidates": {
                    "minItems": 1,
                    "maxItems": 500,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "predicate": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200,
                          "description": "Dotted lower_snake predicate. Use an Ingest predicate (lease.expiration_date, lease.base_rent_schedule, ...) for its value-shape and quote rules; any other predicate gets the generic rules."
                        },
                        "value": {
                          "description": "The value you read, in the predicate's shape: dates YYYY-MM-DD, money and areas as plain numbers (1250000, not \"$1.25M\"), shares and rates as decimals (0.03 for 3%)."
                        },
                        "quote": {
                          "type": "string",
                          "maxLength": 5000,
                          "description": "Verbatim text from the page that states the value, at least 12 characters. Copy it exactly; only whitespace, line breaks, letter case and typographic quotes and dashes may differ."
                        },
                        "page": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 100000,
                          "description": "1-based page the quote is on."
                        },
                        "section": {
                          "description": "Section or clause as written, e.g. \"§4.2(b)\".",
                          "type": "string",
                          "maxLength": 200
                        },
                        "confidence": {
                          "description": "Your confidence 0–1. Below 0.8 the candidate goes to needs_review even if it verifies.",
                          "type": "number",
                          "minimum": 0,
                          "maximum": 1
                        },
                        "commencement": {
                          "description": "Only for lease.base_rent_schedule written by lease year (steps {lease_years: [1, 5], annual_rent_psf}): the commencement date the step dates are derived from, with its own quote. FactLot checks it like a lease.commencement_date candidate and derives each step: Lease Year N runs from the (N−1)th anniversary of the commencement date to the day before the next. If it does not verify, the schedule stays in needs_review.",
                          "type": "object",
                          "properties": {
                            "value": {
                              "type": "string",
                              "format": "date",
                              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                              "description": "The commencement date (YYYY-MM-DD)."
                            },
                            "quote": {
                              "type": "string",
                              "maxLength": 5000,
                              "description": "Verbatim text that states the commencement date, at least 12 characters."
                            },
                            "page": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 100000,
                              "description": "1-based page of this document the quote is on (it may differ from the rent table's page)."
                            },
                            "section": {
                              "description": "Section, if any.",
                              "type": "string",
                              "maxLength": 200
                            }
                          },
                          "required": [
                            "value",
                            "quote",
                            "page"
                          ]
                        },
                        "confirmation": {
                          "description": "A person's confirmation of the parts of the value no quote can prove (a true/false value, an enum such as a CAM cap method or kick-out party, a paraphrased summary). With it, a candidate whose only gaps are such parts is verified with method human_verified: the parts the quote confirmed are listed under checked and the rest under confirmed_by_person. It never overrides a quote that is not on the page, a number or date the quote does not state, or a confidence below 0.8.",
                          "type": "object",
                          "properties": {
                            "by": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200,
                              "description": "The name or role of the PERSON who read the clause and confirmed the value, e.g. \"J. Smith, lease administrator\". FactLot does not authenticate it. Never set this for a value no person has read: an agent must not confirm its own extraction."
                            },
                            "at": {
                              "anyOf": [
                                {
                                  "type": "string",
                                  "format": "date",
                                  "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                                },
                                {
                                  "type": "string",
                                  "format": "date-time",
                                  "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
                                }
                              ],
                              "description": "When the person confirmed it: YYYY-MM-DD, or an ISO 8601 timestamp with offset. A date is recorded on the fact as that day at 00:00 UTC."
                            },
                            "note": {
                              "description": "What the person checked; returned with the result (the fact schema has no field for it).",
                              "type": "string",
                              "maxLength": 1000
                            }
                          },
                          "required": [
                            "by",
                            "at"
                          ]
                        }
                      },
                      "required": [
                        "predicate",
                        "value",
                        "quote",
                        "page"
                      ]
                    },
                    "description": "Values you extracted, each with its quote and page."
                  },
                  "as_of": {
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                    "description": "Date the facts are stated as of (YYYY-MM-DD), usually today."
                  },
                  "subject": {
                    "type": "string",
                    "maxLength": 500,
                    "pattern": "^fl:[a-z]+:[^\\s:]+(?::[^\\s]+)?$",
                    "description": "FactLot URN the facts are about, e.g. your lease's fl:lease:… ID. Default: the document ID."
                  }
                },
                "required": [
                  "document",
                  "candidates",
                  "as_of"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/doc/abstract_lease": {
      "post": {
        "operationId": "doc_abstract_lease",
        "summary": "Lease abstract with amendments",
        "description": "Current-state lease abstract from a lease and its amendments, with amendment precedence, as of as_of. Give each document's pages with the candidates you extracted (checked exactly as doc_verify_facts checks them), or the facts doc_verify_facts already returned for it (without pages only the value against its quote is rechecked, marked quote_on_page: false). Documents may come in any order: give the lease role original and each amendment role amendment with its effective_date (without a role the lease is inferred only when that is unambiguous, otherwise it is an input error; two or more amendments need effective dates). The original applies first, then amendments by effective_date; a later document's value for a predicate controls, and the earlier one moves to history and is linked through supersedes. An amendment effective after as_of is pending: its values are listed under pending and pending_facts with a warning and do not control. A rent schedule restated from a later date keeps the earlier steps before it (carried_forward). Each field gives its value, the controlling document, page, quote and fact, the superseded history, conflicts (two different values from one document, or from two documents of the same rank), pending values and open_items (a candidate for that predicate in the controlling or a later document that needs review or was rejected, so the value may not be final). FactLot only orders what you extracted: it does not find terms you missed, and an amendment that changes a term without restating it (\"extended by 24 months\") needs the resulting value extracted with that sentence as its quote, which the quote check will hold for review if the date is not written there.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "documents": {
                    "minItems": 1,
                    "maxItems": 50,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "role": {
                          "description": "Give it. Without one, a document is the original only when no document is marked original and it is the only one without a role (or without a role and an effective_date); otherwise it is an amendment, and input that leaves the original unclear is an error. Input order is never used.",
                          "type": "string",
                          "enum": [
                            "original",
                            "amendment"
                          ]
                        },
                        "effective_date": {
                          "description": "Amendment effective date (YYYY-MM-DD), required when there are two or more amendments: they apply in date order, and amendments effective the same day do not supersede each other (a different value is a conflict). An amendment effective after as_of is pending: its values are listed under pending and do not control the current abstract. Becomes valid_from on its facts.",
                          "type": "string",
                          "format": "date",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                        },
                        "title": {
                          "type": "string",
                          "maxLength": 500,
                          "description": "Document title for citations, e.g. \"First Amendment to Lease\"."
                        },
                        "sha256": {
                          "type": "string",
                          "pattern": "^[a-f0-9]{64}$",
                          "description": "Optional SHA-256 (lowercase hex) of the original file. When given, document_id is fl:document:sha256-<this>, the same ID id_document gives the file; FactLot cannot check that the pages came from that file. Without it, document_id is the SHA-256 of the pages joined by form feeds (\\f)."
                        },
                        "pages": {
                          "minItems": 1,
                          "maxItems": 2000,
                          "type": "array",
                          "items": {
                            "type": "string",
                            "maxLength": 100000,
                            "description": "The text of one page, exactly as extracted."
                          },
                          "description": "The document's text, one string per page in order: pages[0] is page 1. Send the text as extracted (from the PDF text layer or OCR), not a summary; quotes are matched against it."
                        },
                        "ocr_pages": {
                          "maxItems": 2000,
                          "type": "array",
                          "items": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 100000
                          },
                          "description": "Optional: pages (1-based) whose text came from OCR rather than the file's text layer (doc_read_pdf and factlot.read_pdf report them). Facts and citations on these pages carry ocr: true, so a reviewer checks the scan."
                        },
                        "candidates": {
                          "maxItems": 500,
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "predicate": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 200,
                                "description": "Dotted lower_snake predicate. Use an Ingest predicate (lease.expiration_date, lease.base_rent_schedule, ...) for its value-shape and quote rules; any other predicate gets the generic rules."
                              },
                              "value": {
                                "description": "The value you read, in the predicate's shape: dates YYYY-MM-DD, money and areas as plain numbers (1250000, not \"$1.25M\"), shares and rates as decimals (0.03 for 3%)."
                              },
                              "quote": {
                                "type": "string",
                                "maxLength": 5000,
                                "description": "Verbatim text from the page that states the value, at least 12 characters. Copy it exactly; only whitespace, line breaks, letter case and typographic quotes and dashes may differ."
                              },
                              "page": {
                                "type": "integer",
                                "minimum": 1,
                                "maximum": 100000,
                                "description": "1-based page the quote is on."
                              },
                              "section": {
                                "description": "Section or clause as written, e.g. \"§4.2(b)\".",
                                "type": "string",
                                "maxLength": 200
                              },
                              "confidence": {
                                "description": "Your confidence 0–1. Below 0.8 the candidate goes to needs_review even if it verifies.",
                                "type": "number",
                                "minimum": 0,
                                "maximum": 1
                              },
                              "commencement": {
                                "description": "Only for lease.base_rent_schedule written by lease year (steps {lease_years: [1, 5], annual_rent_psf}): the commencement date the step dates are derived from, with its own quote. FactLot checks it like a lease.commencement_date candidate and derives each step: Lease Year N runs from the (N−1)th anniversary of the commencement date to the day before the next. If it does not verify, the schedule stays in needs_review.",
                                "type": "object",
                                "properties": {
                                  "value": {
                                    "type": "string",
                                    "format": "date",
                                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                                    "description": "The commencement date (YYYY-MM-DD)."
                                  },
                                  "quote": {
                                    "type": "string",
                                    "maxLength": 5000,
                                    "description": "Verbatim text that states the commencement date, at least 12 characters."
                                  },
                                  "page": {
                                    "type": "integer",
                                    "minimum": 1,
                                    "maximum": 100000,
                                    "description": "1-based page of this document the quote is on (it may differ from the rent table's page)."
                                  },
                                  "section": {
                                    "description": "Section, if any.",
                                    "type": "string",
                                    "maxLength": 200
                                  }
                                },
                                "required": [
                                  "value",
                                  "quote",
                                  "page"
                                ]
                              },
                              "confirmation": {
                                "description": "A person's confirmation of the parts of the value no quote can prove (a true/false value, an enum such as a CAM cap method or kick-out party, a paraphrased summary). With it, a candidate whose only gaps are such parts is verified with method human_verified: the parts the quote confirmed are listed under checked and the rest under confirmed_by_person. It never overrides a quote that is not on the page, a number or date the quote does not state, or a confidence below 0.8.",
                                "type": "object",
                                "properties": {
                                  "by": {
                                    "type": "string",
                                    "minLength": 1,
                                    "maxLength": 200,
                                    "description": "The name or role of the PERSON who read the clause and confirmed the value, e.g. \"J. Smith, lease administrator\". FactLot does not authenticate it. Never set this for a value no person has read: an agent must not confirm its own extraction."
                                  },
                                  "at": {
                                    "anyOf": [
                                      {
                                        "type": "string",
                                        "format": "date",
                                        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                                      },
                                      {
                                        "type": "string",
                                        "format": "date-time",
                                        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
                                      }
                                    ],
                                    "description": "When the person confirmed it: YYYY-MM-DD, or an ISO 8601 timestamp with offset. A date is recorded on the fact as that day at 00:00 UTC."
                                  },
                                  "note": {
                                    "description": "What the person checked; returned with the result (the fact schema has no field for it).",
                                    "type": "string",
                                    "maxLength": 1000
                                  }
                                },
                                "required": [
                                  "by",
                                  "at"
                                ]
                              }
                            },
                            "required": [
                              "predicate",
                              "value",
                              "quote",
                              "page"
                            ]
                          },
                          "description": "Values you extracted, each with its quote and page."
                        },
                        "facts": {
                          "description": "Facts doc_verify_facts already returned for this document. With pages, each is checked again; without pages (to stay under the request size limit), only its value against its quote is checked and the abstract marks it quote_on_page: false.",
                          "maxItems": 500,
                          "type": "array",
                          "items": {
                            "type": "object",
                            "propertyNames": {
                              "type": "string"
                            },
                            "additionalProperties": {},
                            "description": "A FactLot fact, as doc_verify_facts returned it."
                          }
                        }
                      }
                    },
                    "description": "The lease and its amendments. Each needs pages with candidates or facts, or facts alone."
                  },
                  "as_of": {
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
                    "description": "Date the facts are stated as of (YYYY-MM-DD), usually today."
                  },
                  "subject": {
                    "type": "string",
                    "maxLength": 500,
                    "pattern": "^fl:[a-z]+:[^\\s:]+(?::[^\\s]+)?$",
                    "description": "FactLot URN the facts are about, e.g. your lease's fl:lease:… ID. Default: the document ID."
                  }
                },
                "required": [
                  "documents",
                  "as_of"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/doc/compare": {
      "post": {
        "operationId": "doc_compare",
        "summary": "Compare asserted values with a lease",
        "description": "Compare values another document asserts (an estoppel, rent roll or offering memo, mapped by you to predicates) with verified lease facts from doc_abstract_lease or doc_verify_facts. Each asserted value comes back match, mismatch (with the differing fields), not_in_lease, or ambiguous_in_lease (two current lease facts disagree), with the controlling lease citation (document, page, quote). Values compare exactly as JSON: numbers by value, strings exactly (a name written differently is a mismatch), and for an object only the fields you assert. Facts another fact supersedes are ignored. When the lease fact is an extracted fact whose quote does not confirm part of its value (a true/false value, an enum), the result lists those parts in lease_not_checked. The lease facts must pass fact_validate; FactLot does not re-read the lease, so pass the facts its document tools returned.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lease_facts": {
                    "minItems": 1,
                    "maxItems": 2000,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "propertyNames": {
                        "type": "string"
                      },
                      "additionalProperties": {},
                      "description": "A FactLot fact, as doc_verify_facts returned it."
                    },
                    "description": "Verified lease facts: the facts from doc_abstract_lease (or doc_verify_facts). Facts another fact supersedes are ignored."
                  },
                  "asserted": {
                    "minItems": 1,
                    "maxItems": 500,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "predicate": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200,
                          "description": "Predicate of the asserted value, e.g. lease.expiration_date."
                        },
                        "value": {
                          "description": "The asserted value in the predicate's shape (YYYY-MM-DD dates, plain numbers, decimal rates). For an object, only the fields given are compared."
                        },
                        "label": {
                          "description": "Where it was asserted, e.g. \"Estoppel ¶3\".",
                          "type": "string",
                          "maxLength": 200
                        }
                      },
                      "required": [
                        "predicate",
                        "value"
                      ]
                    },
                    "description": "Values another document states (an estoppel, a rent roll, an OM), mapped to predicates."
                  }
                },
                "required": [
                  "lease_facts",
                  "asserted"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/doc/verify_claim": {
      "post": {
        "operationId": "doc_verify_claim",
        "summary": "Verify a claim against a document",
        "description": "Check one claim against a document page: {predicate, value} (anything doc_verify_facts accepts) or a short claim sentence ({text} or a bare string), plus the verbatim quote and page it rests on. Returns supported (the quote is on the page and every checked number, date and name in the claim is written in it), contradicted (the quote is on the page and states other numbers or dates of the same kind, listed in quote_states), or unverifiable (the quote is not on the page, nothing in the claim can be checked, or the quote states no value of that kind), with the evidence (document_id, page, quote, span) and each part checked. Textual, not semantic: for a sentence only its numbers and dates are compared, never its meaning, and a quote that mentions the right number for a different term still supports it.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "document": {
                    "type": "object",
                    "properties": {
                      "pages": {
                        "minItems": 1,
                        "maxItems": 2000,
                        "type": "array",
                        "items": {
                          "type": "string",
                          "maxLength": 100000,
                          "description": "The text of one page, exactly as extracted."
                        },
                        "description": "The document's text, one string per page in order: pages[0] is page 1. Send the text as extracted (from the PDF text layer or OCR), not a summary; quotes are matched against it."
                      },
                      "sha256": {
                        "type": "string",
                        "pattern": "^[a-f0-9]{64}$",
                        "description": "Optional SHA-256 (lowercase hex) of the original file. When given, document_id is fl:document:sha256-<this>, the same ID id_document gives the file; FactLot cannot check that the pages came from that file. Without it, document_id is the SHA-256 of the pages joined by form feeds (\\f)."
                      },
                      "title": {
                        "type": "string",
                        "maxLength": 500,
                        "description": "Document title for citations, e.g. \"First Amendment to Lease\"."
                      },
                      "ocr_pages": {
                        "maxItems": 2000,
                        "type": "array",
                        "items": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 100000
                        },
                        "description": "Optional: pages (1-based) whose text came from OCR rather than the file's text layer (doc_read_pdf and factlot.read_pdf report them). Facts and citations on these pages carry ocr: true, so a reviewer checks the scan."
                      }
                    },
                    "required": [
                      "pages"
                    ],
                    "description": "The document the quote is from."
                  },
                  "claim": {
                    "anyOf": [
                      {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 2000,
                        "description": "A short claim sentence, read as {text: the sentence}; its numbers and dates are checked."
                      },
                      {
                        "type": "object",
                        "properties": {
                          "predicate": {
                            "description": "Predicate of the claim, with value.",
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          },
                          "value": {
                            "description": "Claimed value in the predicate's shape, with predicate."
                          },
                          "text": {
                            "description": "Or a short claim sentence instead of predicate and value; its numbers and dates are checked.",
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 2000
                          }
                        },
                        "description": "Either {predicate, value} or {text}."
                      }
                    ]
                  },
                  "quote": {
                    "type": "string",
                    "maxLength": 5000,
                    "description": "Verbatim text from the page that the claim rests on."
                  },
                  "page": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100000,
                    "description": "1-based page the quote is on."
                  }
                },
                "required": [
                  "document",
                  "claim",
                  "quote",
                  "page"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/doc/spread_statement": {
      "post": {
        "operationId": "doc_spread_statement",
        "summary": "Spread an operating statement",
        "description": "Spread a T-12, operating statement, budget or rent roll summary YOU read, onto FactLot's published chart of accounts (multifamily, commercial, or hotel after the USALI summary), deterministically; FactLot runs no model. Send the pages as text and one line per item: {label, amount, quote, page}, optionally your category, a period column, and for a subtotal the indexes of the lines it sums; give the statement's own totals (effective_gross_income, total_operating_expenses, net_operating_income, ebitda, ...) as lines with that total as category. Each line is checked: the quote is on the page, the amount (as a number, any common written form; (1,200) and -1,200 read as negative) and the label are written in it; a line that fails is excluded with the reason. The label is mapped to the chart by keywords: your category is used when it is in the chart, and an exception flags it for review when the dictionary maps the label elsewhere; an ambiguous or unknown label without a category is excluded. Vacancy, concessions, credit loss and other contra lines are always subtracted. Returns per period the sections, the computed totals (EGI, operating expenses, NOI, capital items, net cash flow; for hotels departmental profit, GOP, EBITDA and NOI after the replacement reserve), whether each total and subtotal the statement states foots (within 0.5 per line added, or your tolerance), every line with its citation (document, page, quote, span) and status, statistics (units, occupancy, ADR), and exceptions to review. Not checked: whether the agent missed lines, and meaning beyond the written amount and label.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "document": {
                    "type": "object",
                    "properties": {
                      "pages": {
                        "minItems": 1,
                        "maxItems": 2000,
                        "type": "array",
                        "items": {
                          "type": "string",
                          "maxLength": 100000,
                          "description": "The text of one page, exactly as extracted."
                        },
                        "description": "The document's text, one string per page in order: pages[0] is page 1. Send the text as extracted (from the PDF text layer or OCR), not a summary; quotes are matched against it."
                      },
                      "sha256": {
                        "type": "string",
                        "pattern": "^[a-f0-9]{64}$",
                        "description": "Optional SHA-256 (lowercase hex) of the original file. When given, document_id is fl:document:sha256-<this>, the same ID id_document gives the file; FactLot cannot check that the pages came from that file. Without it, document_id is the SHA-256 of the pages joined by form feeds (\\f)."
                      },
                      "title": {
                        "type": "string",
                        "maxLength": 500,
                        "description": "Document title for citations, e.g. \"First Amendment to Lease\"."
                      },
                      "ocr_pages": {
                        "maxItems": 2000,
                        "type": "array",
                        "items": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 100000
                        },
                        "description": "Optional: pages (1-based) whose text came from OCR rather than the file's text layer (doc_read_pdf and factlot.read_pdf report them). Facts and citations on these pages carry ocr: true, so a reviewer checks the scan."
                      }
                    },
                    "required": [
                      "pages"
                    ],
                    "description": "The statement's pages."
                  },
                  "property_type": {
                    "type": "string",
                    "enum": [
                      "multifamily",
                      "commercial",
                      "hotel"
                    ],
                    "description": "Which chart of accounts to use: multifamily, commercial (office, retail, industrial), or hotel (USALI summary)."
                  },
                  "statement_type": {
                    "description": "What the document is; echoed back.",
                    "type": "string",
                    "enum": [
                      "t12",
                      "operating_statement",
                      "budget",
                      "rent_roll_summary",
                      "other"
                    ]
                  },
                  "period": {
                    "description": "Default period label for lines that give none.",
                    "type": "string",
                    "maxLength": 100
                  },
                  "lines": {
                    "minItems": 1,
                    "maxItems": 1000,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "label": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 300,
                          "description": "The line's label exactly as the statement writes it, e.g. \"Real Estate Taxes\". It must appear in the quote."
                        },
                        "amount": {
                          "type": "number",
                          "description": "The amount as a plain number (245000, not \"$245K\"). Write a deduction as the statement shows it; vacancy, concessions and other contra lines are always subtracted."
                        },
                        "quote": {
                          "type": "string",
                          "maxLength": 5000,
                          "description": "Verbatim text from the page with the label and the amount, e.g. \"Real Estate Taxes 245,000\"."
                        },
                        "page": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 100000,
                          "description": "1-based page the quote is on."
                        },
                        "category": {
                          "description": "Your proposed category from the chart for property_type (spec/schemas/vocabularies/chart-of-accounts.json), e.g. real_estate_taxes, or a total such as net_operating_income for a total line. Without it, FactLot maps the label itself.",
                          "type": "string",
                          "maxLength": 100
                        },
                        "period": {
                          "description": "Period column the amount is from, e.g. \"T-12 Sep 2026\" or \"2025\". Lines are spread per period. Default: the statement's period.",
                          "type": "string",
                          "maxLength": 100
                        },
                        "sums": {
                          "description": "For a subtotal the statement prints (\"Total Utilities\"): the indexes (0-based) of the lines it adds up. FactLot checks that it foots.",
                          "minItems": 1,
                          "maxItems": 1000,
                          "type": "array",
                          "items": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 999
                          }
                        }
                      },
                      "required": [
                        "label",
                        "amount",
                        "quote",
                        "page"
                      ]
                    },
                    "description": "The statement's lines as you read them: every income, expense and capital line, the statement's own subtotals (with sums) and totals (with a total category), and statistics such as units or occupancy."
                  },
                  "tolerance": {
                    "description": "Largest difference, in the statement's units, at which a subtotal or total still foots. Default: 0.5 for each line added (rounding).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1000000000
                  }
                },
                "required": [
                  "document",
                  "property_type",
                  "lines"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/doc/check_use_conflict": {
      "post": {
        "operationId": "doc_check_use_conflict",
        "summary": "Check a proposed use against exclusives",
        "description": "Check a proposed use (a description, plus an optional use category or 2022 NAICS code) against a center's verified lease.exclusive_use and lease.prohibited_uses facts (from doc_verify_facts or doc_abstract_lease; each must pass fact_validate). Deterministic and textual: a restriction is a potential conflict when it shares a use category with the proposed use (FactLot's published use-category vocabulary, matched on keywords, the category given and NAICS codes) or shares words with it (two or more, or every significant word of the restriction). Each hit is labelled \"potential conflict — needs human review\" and returns the controlling quote and citation, the categories and words that matched, and the exclusive's carve-outs, flagging those that mention the proposed use. FactLot does not interpret the lease, decide whether a carve-out applies, or give legal conclusions; an empty result does not mean there is no conflict. Superseded facts are ignored.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "facts": {
                    "minItems": 1,
                    "maxItems": 2000,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "propertyNames": {
                        "type": "string"
                      },
                      "additionalProperties": {},
                      "description": "A FactLot fact, as doc_verify_facts or doc_abstract_lease returned it."
                    },
                    "description": "Verified lease.exclusive_use and lease.prohibited_uses facts for the center, from doc_verify_facts or doc_abstract_lease (each must pass fact_validate). Facts another fact supersedes are ignored; other predicates are skipped."
                  },
                  "proposed_use": {
                    "type": "object",
                    "properties": {
                      "text": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 2000,
                        "description": "The proposed use as described, e.g. \"coffee shop selling espresso drinks and pastries\"."
                      },
                      "category": {
                        "description": "Optional use category id from FactLot's vocabulary (spec/schemas/vocabularies/use-categories.json), e.g. coffee.",
                        "type": "string",
                        "maxLength": 100
                      },
                      "naics": {
                        "description": "Optional 2022 NAICS code, 2 to 6 digits; a shorter code matches every category under it (7225 matches restaurants and coffee shops).",
                        "type": "string",
                        "pattern": "^[0-9]{2,6}$"
                      }
                    },
                    "required": [
                      "text"
                    ],
                    "description": "The use to check."
                  }
                },
                "required": [
                  "facts",
                  "proposed_use"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/doc/predicates": {
      "post": {
        "operationId": "doc_predicates",
        "summary": "Lease predicate catalog",
        "description": "Look up the lease predicates doc_verify_facts and doc_abstract_lease know (lease.expiration_date, lease.base_rent_schedule, lease.go_dark, ...) before extracting: for each, its value shape as JSON Schema, one valid example with a quote that states it (and whether that example verifies), which parts of the value are checked against the quote and how (number, ratio, date, verbatim name, as-written words, a worded enum, a number inside free text), which count as checked only when the quote writes them out (summaries, some enums), and which always need review (true/false values and enums no wording confirms, such as a CAM cap method or a kick-out party) unless a person confirms them. Generated from the same tables the checks use. Give predicate for one, prefix (e.g. lease.renewal) for a group, or neither for all. Also returns notes on statuses, paths, human confirmation and the generic rules for any other dotted predicate. Reads no document.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "predicate": {
                    "description": "One predicate to describe, e.g. lease.go_dark. Omit both predicate and prefix for every lease predicate.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "prefix": {
                    "description": "Only predicates starting with this, e.g. \"lease.renewal\" or \"lease.\". Give predicate or prefix, not both.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/facts/package": {
      "post": {
        "operationId": "fact_package_create",
        "summary": "Create a fact package",
        "description": "Bundle FactLot facts (each must pass fact_validate) into a fact package a counterparty can verify: canonical JSON (RFC 8785) of every fact, an RFC 6962 Merkle tree over the facts, the source documents the facts cite with their sha256 where known, and a digest over all of it. Returns the unsigned package, its digest, and signing_message, the exact text to sign. FactLot never holds private keys: to sign, the holder signs signing_message with their own Ed25519 key on their own machine (factlot.sign_fact_package in Python, signFactPackage in @factlot/tools, or fact_package_sign in the local MCP server). Deterministic: no clock time is added (pass created_at), and the same facts always give the same digest.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "facts": {
                    "minItems": 1,
                    "maxItems": 2000,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "propertyNames": {
                        "type": "string"
                      },
                      "additionalProperties": {},
                      "description": "A FactLot fact (it must pass fact_validate)."
                    },
                    "description": "The facts to package, in order: verified facts from the doc_* tools or any facts that pass fact_validate."
                  },
                  "title": {
                    "description": "A title for the package.",
                    "type": "string",
                    "maxLength": 500
                  },
                  "created_at": {
                    "description": "When the package was made (ISO 8601 with offset). FactLot adds no clock time, so the same input always gives the same package.",
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
                  },
                  "created_by": {
                    "description": "The organization making the package.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "documents": {
                    "description": "Source documents to describe beyond what the facts cite (a title, the file's sha256). Every document a fact cites is listed automatically.",
                    "maxItems": 50,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "document_id": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 500,
                          "description": "The document's ID as the facts cite it."
                        },
                        "sha256": {
                          "description": "SHA-256 of the exact file, when known.",
                          "type": "string",
                          "pattern": "^[a-f0-9]{64}$"
                        },
                        "title": {
                          "type": "string",
                          "maxLength": 500
                        }
                      },
                      "required": [
                        "document_id"
                      ]
                    }
                  }
                },
                "required": [
                  "facts"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/facts/package/verify": {
      "post": {
        "operationId": "fact_package_verify",
        "summary": "Verify a fact package",
        "description": "Verify a fact package from fact_package_create: recompute every fact's hash, the Merkle root and the digest from the content (any changed fact, title, date or document list shows as a mismatch); check each fact against fact_validate; check the Ed25519 signature, against the public_key you pass (base64url, the signer's key you trust) or, without one, only against the key inside the package (which proves nothing about who signed); and, given the source documents' pages, check every quote is on its cited page. Returns valid plus each check's details, checks_run and checks_not_run (what was and was not checked), and values_not_checked: the parts of extracted facts' values that their quotes do not confirm (a true/false value, an enum, a summary), which do not change valid. Not checked: that the pages you pass are the original files (pass sha256 to tie them to the file hash), or what the facts mean.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "package": {
                    "type": "object",
                    "propertyNames": {
                      "type": "string"
                    },
                    "additionalProperties": {},
                    "description": "A fact package from fact_package_create, signed or not."
                  },
                  "public_key": {
                    "description": "The signer's Ed25519 public key you trust: 32 bytes as base64url (a JWK's x). With it the signature must verify for the package to be valid.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "documents": {
                    "description": "Optional: the source documents' pages (with sha256 when the facts cite the file's hash). Each fact's quote is then checked on its cited page.",
                    "maxItems": 50,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "pages": {
                          "minItems": 1,
                          "maxItems": 2000,
                          "type": "array",
                          "items": {
                            "type": "string",
                            "maxLength": 100000,
                            "description": "The text of one page, exactly as extracted."
                          },
                          "description": "The document's text, one string per page in order: pages[0] is page 1. Send the text as extracted (from the PDF text layer or OCR), not a summary; quotes are matched against it."
                        },
                        "sha256": {
                          "type": "string",
                          "pattern": "^[a-f0-9]{64}$",
                          "description": "Optional SHA-256 (lowercase hex) of the original file. When given, document_id is fl:document:sha256-<this>, the same ID id_document gives the file; FactLot cannot check that the pages came from that file. Without it, document_id is the SHA-256 of the pages joined by form feeds (\\f)."
                        },
                        "title": {
                          "type": "string",
                          "maxLength": 500,
                          "description": "Document title for citations, e.g. \"First Amendment to Lease\"."
                        },
                        "ocr_pages": {
                          "maxItems": 2000,
                          "type": "array",
                          "items": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 100000
                          },
                          "description": "Optional: pages (1-based) whose text came from OCR rather than the file's text layer (doc_read_pdf and factlot.read_pdf report them). Facts and citations on these pages carry ocr: true, so a reviewer checks the scan."
                        }
                      },
                      "required": [
                        "pages"
                      ]
                    }
                  }
                },
                "required": [
                  "package"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/comps/validate": {
      "post": {
        "operationId": "lease_comp_validate",
        "summary": "Validate a lease comp and derive its NER",
        "description": "Check a lease comparable one firm shares with others, as a FactLot fact with predicate lease.comp, and derive its net effective rent. The fact goes through fact_validate (schema, citations, rights), then the comp's own rules: shape (spec/schemas/fact.json LeaseComp: area, commencement, term or expiration, starting or current rent, escalation, free rent, TI, leasing commissions, expense basis, confirming party, verification), an expiration after commencement, a term that matches the dates, free rent within the term, no email, phone or ID number in the text. A comp names no person or company. With the default share profile it also needs the deal's starting rent, escalations, free rent, TI, property type, an expense basis other than unknown, and stated rights. When valid and a starting rent is given, ner.fact is a computed lease.net_effective_rent fact (formula calc.net_effective_rent@1, straight-line and discounted at discount_rate_annual, on the comp's own expense basis; no conversion between full service and net). Returns { valid, issues, warnings, ner }. Call before sharing a comp.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fact": {
                    "type": "object",
                    "propertyNames": {
                      "type": "string"
                    },
                    "additionalProperties": {},
                    "description": "A FactLot fact with predicate lease.comp: subject (an fl:lease: URN), value (the comp), as_of, provenance (with at least one source: your own record is fine, cited by document_id or sha256 without sharing it), and rights (ai_use and redistribution)."
                  },
                  "profile": {
                    "default": "share",
                    "description": "share (default): a comp offered to other firms: needs the deal's starting rent, escalations, free rent, TI, expense basis, property type, a term and stated rights. observed: a rent seen in a public record (current_rent_psf_yr is enough); no NER is derived without a starting rent.",
                    "type": "string",
                    "enum": [
                      "share",
                      "observed"
                    ]
                  },
                  "discount_rate_annual": {
                    "default": 0.08,
                    "description": "Annual discount rate for the discounted NER (an assumption, not part of the comp). 0 gives the straight-line NER only.",
                    "type": "number",
                    "minimum": 0,
                    "exclusiveMaximum": 1
                  }
                },
                "required": [
                  "fact"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/comps/search": {
      "post": {
        "operationId": "lease_comp_search",
        "summary": "Search GSA lease comps (federal tenant)",
        "description": "Search the 6,994 lease comps from the U.S. General Services Administration's Lease Inventory (October 2026 file, FactLot release lease-comps-gsa-2026-11-04), bundled with FactLot: works offline and on the hosted API, no registry needed. READ THE CAVEAT: the tenant in every one of these leases is the federal government (GSA, for an agency); they are not market comps for private tenants, and the rent is the current annual rent in GSA's file divided by the rentable SF, not the starting rent (no escalations, free rent, TI or commissions are known). Filter by state, 5-digit county FIPS, city, ZIP, a radius around a latitude/longitude (GSA's own coordinates; nothing is geocoded), rentable SF, commencement date, rent per SF, property type (office, industrial, other) and expense basis; sort; limit (default 25, at most 200). Each comp gives its FactLot lease ID, GSA lease number, address, county FIPS, building ID, landlord (organisations only; individuals are never stored), rentable SF, commencement, expiration, term, early-termination date, annual rent, rent per SF per year, property type, expense basis and the row of the GSA file it comes from. summary gives the count and the median, quartiles and range of rent per SF over every match, labelled as statistics of GSA leases. Every answer carries the caveat and the citation (GSA file URL, SHA-256 document ID, as-of and retrieval dates, licence: public domain). Cite them, and never mix these comps silently into market comps.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "state": {
                    "description": "Two-letter state or territory code, e.g. \"TX\".",
                    "type": "string",
                    "pattern": "^[A-Za-z]{2}$"
                  },
                  "county_fips": {
                    "description": "5-digit county FIPS code (state + county), e.g. \"48201\" for Harris County, Texas. Connecticut uses its planning regions (09110 to 09190).",
                    "type": "string",
                    "pattern": "^[0-9]{5}$"
                  },
                  "city": {
                    "description": "City as GSA writes it, matched whole and ignoring case, e.g. \"Houston\".",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "zip": {
                    "description": "5-digit ZIP code (ZIP+4 accepted), e.g. \"77002\".",
                    "type": "string",
                    "pattern": "^[0-9]{5}(-[0-9]{4})?$"
                  },
                  "near": {
                    "description": "Comps within a radius of a point, by great-circle distance to the coordinates GSA's file gives for each lease (FactLot geocodes nothing; give the point's coordinates yourself). Comps without coordinates are left out and counted in warnings.",
                    "type": "object",
                    "properties": {
                      "latitude": {
                        "type": "number",
                        "minimum": -90,
                        "maximum": 90,
                        "description": "Latitude, degrees."
                      },
                      "longitude": {
                        "type": "number",
                        "minimum": -180,
                        "maximum": 180,
                        "description": "Longitude, degrees."
                      },
                      "radius_miles": {
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "maximum": 500,
                        "description": "Radius in miles (at most 500)."
                      }
                    },
                    "required": [
                      "latitude",
                      "longitude",
                      "radius_miles"
                    ]
                  },
                  "min_rentable_sf": {
                    "description": "Smallest lease rentable area, SF.",
                    "type": "number",
                    "minimum": 0
                  },
                  "max_rentable_sf": {
                    "description": "Largest lease rentable area, SF.",
                    "type": "number",
                    "minimum": 0
                  },
                  "commencement_from": {
                    "description": "Earliest lease commencement (effective) date, YYYY-MM-DD.",
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                  },
                  "commencement_to": {
                    "description": "Latest lease commencement (effective) date, YYYY-MM-DD.",
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                  },
                  "min_rent_psf_yr": {
                    "description": "Lowest current rent, USD per rentable SF per year (GSA's current annual rent divided by the RSF).",
                    "type": "number",
                    "minimum": 0
                  },
                  "max_rent_psf_yr": {
                    "description": "Highest current rent, USD per rentable SF per year.",
                    "type": "number",
                    "minimum": 0
                  },
                  "property_type": {
                    "description": "office, industrial (GSA warehouse) or other (GSA special space): the largest use in the lease.",
                    "type": "string",
                    "enum": [
                      "office",
                      "industrial",
                      "other"
                    ]
                  },
                  "expense_basis": {
                    "description": "full_service (GSA fully serviced) or not_full_service.",
                    "type": "string",
                    "enum": [
                      "full_service",
                      "not_full_service"
                    ]
                  },
                  "sort": {
                    "description": "Order of results: distance_asc (the default with near; needs near), commencement_desc (the default otherwise), commencement_asc, expiration_asc, expiration_desc, rent_psf_desc, rent_psf_asc, rentable_sf_desc, rentable_sf_asc. Ties go by GSA lease number.",
                    "type": "string",
                    "enum": [
                      "commencement_desc",
                      "commencement_asc",
                      "expiration_asc",
                      "expiration_desc",
                      "rent_psf_desc",
                      "rent_psf_asc",
                      "rentable_sf_desc",
                      "rentable_sf_asc",
                      "distance_asc"
                    ]
                  },
                  "limit": {
                    "default": 25,
                    "description": "Maximum comps returned (1–200, default 25); the summary covers every match.",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/registry/get": {
      "post": {
        "operationId": "registry_get",
        "summary": "Get a registry record",
        "description": "Everything the FactLot Registry knows about a parcel, legal entity, or building: status, current cited facts (owner of record, situs address, property category, areas, values), ownership links, external IDs, and for a parcel the building it belongs to (condominium units roll up into one building). value_history (parcels) lists market and appraised value by tax year from every snapshot, newest first, each value cited; a tax year two snapshots value differently appears once per value. as_of gives the facts as they stood on a date. warnings flags area facts that look wrong (building area above 40 times the land area, a land area of 0); the facts themselves are as the source publishes them. A building record can carry debt.cmbs_loan facts (one per CMBS trust holding a note of its loan: balance, rate, maturity, payment status, servicer, special-servicing dates, NOI, DSCR) and an entity record can carry entity.parent facts (the SEC registrant that lists it as a subsidiary in Exhibit 21), each cited to its SEC filing. A miss says whether the county or entity registry is loaded. Requires a FactLot Registry (public-record facts about commercial property, each cited to the source file and record).",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "urn": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "FactLot URN of a parcel, entity, or building, e.g. fl:parcel:us-48201:0420150000012."
                  },
                  "include_history": {
                    "default": false,
                    "description": "Also return every fact from every snapshot, superseded ones included (history lists current facts too; a source loaded once has no superseded facts).",
                    "type": "boolean"
                  },
                  "as_of": {
                    "description": "Date (YYYY-MM-DD): return the facts and ownership links current on that date, from the snapshots dated on or before it, instead of the latest. With include_history, history stops at that date.",
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                  }
                },
                "required": [
                  "urn"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/registry/resolve_parcel": {
      "post": {
        "operationId": "registry_resolve_parcel",
        "summary": "Resolve a parcel in the registry",
        "description": "Find a parcel's FactLot URN from state FIPS, county FIPS, and APN, if the registry holds it. A miss returns urn null with coverage: whether that county is loaded in this registry. Requires a FactLot Registry (public-record facts about commercial property, each cited to the source file and record).",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "state_fips": {
                    "type": "string",
                    "maxLength": 16,
                    "description": "2-digit state FIPS, e.g. \"48\" for Texas."
                  },
                  "county_fips": {
                    "type": "string",
                    "maxLength": 16,
                    "description": "3-digit county FIPS, e.g. \"201\" for Harris County."
                  },
                  "apn": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Parcel or account number as the county prints it."
                  }
                },
                "required": [
                  "state_fips",
                  "county_fips",
                  "apn"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/registry/resolve_external": {
      "post": {
        "operationId": "registry_resolve_external",
        "summary": "Resolve an external ID",
        "description": "Find the FactLot URN for an ID from another system, e.g. system \"tx-harris-cad-account\" or \"us-tx-taxpayer\". Requires a FactLot Registry (public-record facts about commercial property, each cited to the source file and record).",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "system": {
                    "type": "string",
                    "maxLength": 100,
                    "description": "Crosswalk system name, e.g. \"tx-harris-cad-account\"."
                  },
                  "external_id": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "The ID in that system."
                  }
                },
                "required": [
                  "system",
                  "external_id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/registry/search_address": {
      "post": {
        "operationId": "registry_search_address",
        "summary": "Search parcels by address",
        "description": "Find commercial parcels by situs address. Matches whole words of the street line (the part before the first comma), normalized: 'Street' = 'St', 'Northwest' = 'N.W.' = 'NW'; a suite, unit, '#' or floor is ignored. Give a house number ('1200 Main', '1200 Main St NW') or a street name with its type ('Main St'); a city or a part of a word alone matches nothing. No match returns coverage: what this registry holds. Requires a FactLot Registry (public-record facts about commercial property, each cited to the source file and record).",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "query": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500,
                    "description": "Street address, or a house number and street words, or a street name with its type."
                  },
                  "limit": {
                    "default": 20,
                    "description": "Maximum results (1–100).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100
                  }
                },
                "required": [
                  "query"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/registry/search_entities": {
      "post": {
        "operationId": "registry_search_entities",
        "summary": "Search legal entities by name",
        "description": "Find registered business entities (LLCs, LPs, corporations) by name. Natural persons are not searchable. Requires a FactLot Registry (public-record facts about commercial property, each cited to the source file and record).",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "query": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500,
                    "description": "Entity name or part of one."
                  },
                  "limit": {
                    "default": 20,
                    "description": "Maximum results (1–100).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100
                  }
                },
                "required": [
                  "query"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/registry/owned_by": {
      "post": {
        "operationId": "registry_owned_by",
        "summary": "Parcels owned by an entity",
        "description": "Commercial parcels currently linked to a legal entity as owner of record (inferred links carry a confidence), each with the building it belongs to, plus a per-building summary so condominium units count once per building. Requires a FactLot Registry (public-record facts about commercial property, each cited to the source file and record).",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "entity_urn": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "FactLot entity URN, e.g. fl:entity:us-tx:0801111111."
                  }
                },
                "required": [
                  "entity_urn"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/registry/coverage": {
      "post": {
        "operationId": "registry_coverage",
        "summary": "What the registry holds",
        "description": "The counties (with parcel and building counts), entity registries, and crosswalk systems this FactLot Registry file holds, each with its sources and as-of dates. Call it before concluding that a parcel or entity does not exist: a miss outside these regions means not loaded, not absent. Requires a FactLot Registry (public-record facts about commercial property, each cited to the source file and record).",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {}
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/registry/tx_equity_candidates": {
      "post": {
        "operationId": "registry_tx_equity_candidates",
        "summary": "Texas unequal-appraisal candidates",
        "description": "Candidate comparable parcels for a Texas unequal-appraisal comparison (Tax Code §41.43(b)(3)), from one tax year of the subject's appraisal district roll (the newest year whose roll gives the subject a value, or tax_year), with every field of the subject and the candidates read from that year's roll only: same county and tax year, the same state property category code, improvement area within area_band of the subject's, year built within year_band years when both are known; parcels sharing the subject's neighborhood code first (when the roll gives one), then its ZIP code, then the rest of the county if fewer than min_matches. Each candidate and the subject are shaped for calc_tx_unequal_appraisal (id, value and value_basis, areas, year built, codes, citation of the value fact); the subject adds its value history by tax year. selection echoes the rule, the effective parameters, the tax year and roll sources used, and which tier each candidate came from; Each candidate's citation.document_id names its entry in sources, which gives that snapshot's license, attribution, ai_use and redistribution: relay them with the candidate. Candidates only: a person decides which to use. The selection rule is FactLot's default, not a legal standard of comparability. For evidence calculations only: FactLot does not represent property owners, file protests, or advise whether to protest. Never returns owner names, mailing addresses, or ownership facts. Do not look up the owners of candidate parcels; ownership is not needed for this comparison. Texas parcels only; no Tarrant County parcels until Tarrant Appraisal District's published signals say whether serving its data to AI agents is allowed. Requires a FactLot Registry (public-record facts about commercial property, each cited to the source file and record).",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "urn": {
                    "description": "FactLot parcel URN of the subject, e.g. fl:parcel:us-48201:0420150000012. Give this, or county_fips and apn.",
                    "type": "string",
                    "maxLength": 500
                  },
                  "state_fips": {
                    "description": "With county_fips and apn: \"48\" (Texas), the only state served.",
                    "type": "string",
                    "maxLength": 16
                  },
                  "county_fips": {
                    "description": "3-digit Texas county FIPS, e.g. \"201\" for Harris County.",
                    "type": "string",
                    "maxLength": 16
                  },
                  "apn": {
                    "description": "Appraisal district account number as the district prints it.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "area_band": {
                    "default": 0.35,
                    "description": "Improvement area band as a share of the subject's (0.35: within ±35%).",
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "maximum": 1
                  },
                  "year_band": {
                    "default": 15,
                    "description": "Year-built band in years, applied when both the subject's and the candidate's are known.",
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100
                  },
                  "min_matches": {
                    "default": 10,
                    "description": "Widen from the neighborhood and ZIP code tiers to the whole county when they hold fewer than this.",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 200
                  },
                  "limit": {
                    "default": 50,
                    "description": "Maximum candidates returned (1–200).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 200
                  },
                  "tax_year": {
                    "description": "Roll year to compare in. Default: the newest tax year whose roll gives the subject a positive value.",
                    "type": "integer",
                    "minimum": 1900,
                    "maximum": 2100
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Invalid input; read `issues`, fix the arguments, and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "CalcInputError, FactLotIdError, InputError, or UnknownTool"
                    },
                    "message": {
                      "type": "string"
                    },
                    "issues": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "error",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Only if the server sets FACTLOT_API_KEY."
      }
    }
  }
}