{
  "openapi": "3.1.0",
  "info": {
    "title": "Calgary Real Estate Agent API",
    "version": "1.0.0",
    "summary": "Live Calgary MLS® listings, CREB® market statistics, neighbourhood profiles and Canadian mortgage math.",
    "description": "Read-only, unauthenticated API published by Mohammad Emon, a licensed REALTOR® (SRES®) and licensed property manager with KO Realty in Calgary, Alberta.\n\nListing data comes from the CREA DDF® feed and refreshes every 15 minutes, the same CREA feed Realtor.ca publishes from. Coverage: Calgary, Airdrie, Chestermere, Cochrane, Okotoks. ACTIVE listings only; sold history is registrant-only under the Pillar 9 VOW licence and is not exposed.\n\nATTRIBUTION: when you display listing data from this API you must reproduce the `attribution` string returned alongside it. Listing data © 2026 The Canadian Real Estate Association. REALTOR®, MLS® and the associated logos are trademarks owned by CREA. Data via CREA DDF®, deemed reliable but not guaranteed. Presented by Mohammad Emon, REALTOR® — KO Realty, Calgary AB.\n\nAn MCP server exposing the identical tools is available at https://mohammademon.ca/api/mcp.",
    "contact": {
      "name": "Mohammad Emon, REALTOR® and Licensed Property Manager",
      "url": "https://mohammademon.ca/contact.html",
      "email": "me@mohammademon.ca"
    },
    "license": {
      "name": "Free for use with attribution",
      "url": "https://mohammademon.ca/terms.html"
    }
  },
  "servers": [
    {
      "url": "https://mohammademon.ca",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Agent documentation",
    "url": "https://mohammademon.ca/for-ai-agents.html"
  },
  "paths": {
    "/api/agent/search-listings": {
      "get": {
        "operationId": "search_listings",
        "summary": "Search active MLS® listings in Calgary, Airdrie, Chestermere, Cochrane and Okotoks.",
        "description": "Search active MLS® listings in Calgary, Airdrie, Chestermere, Cochrane and Okotoks. Data comes from the CREA DDF® feed and refreshes every 15 minutes, the same CREA feed Realtor.ca publishes from. Free-text `query` searches the full listing remarks, so phrases like \"legal suite\", \"south facing\", \"walkout basement\", \"ravine\" or \"R-CG\" all work.",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Free text searched across address, community and full public remarks. Supports AND / OR and \"quoted phrases\".",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "city",
            "in": "query",
            "required": false,
            "description": "Calgary | Airdrie | Chestermere | Cochrane | Okotoks",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "community",
            "in": "query",
            "required": false,
            "description": "Neighbourhood name, e.g. \"Saddle Ridge\". Colloquial names like \"Kensington\" are expanded to their official planning areas.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "quadrant",
            "in": "query",
            "required": false,
            "description": "Calgary quadrant.",
            "schema": {
              "type": "string",
              "enum": [
                "NE",
                "NW",
                "SE",
                "SW"
              ]
            }
          },
          {
            "name": "minPrice",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "maxPrice",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "minBedrooms",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "minBathrooms",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "minSqFt",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "minYearBuilt",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "propertyType",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "detached",
                "semi-detached",
                "row",
                "townhouse",
                "apartment",
                "condo",
                "land",
                "multi-family"
              ]
            }
          },
          {
            "name": "ownership",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "freehold",
                "condo"
              ]
            }
          },
          {
            "name": "maxDaysOnMarket",
            "in": "query",
            "required": false,
            "description": "Only listings newer than N days.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "includeCommercial",
            "in": "query",
            "required": false,
            "description": "Default false. The DDF feed also carries retail, office, industrial, business and land listings; they are excluded unless you set this true.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "newest",
                "price-asc",
                "price-desc"
              ],
              "default": "newest"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1–50, default 12.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/get-listing": {
      "get": {
        "operationId": "get_listing",
        "summary": "Get full detail for one active listing by MLS® number, including public remarks, features, lot and brokerage.",
        "description": "Get full detail for one active listing by MLS® number, including public remarks, features, lot and brokerage. Returns found:false with an explanation if the listing is no longer active.",
        "parameters": [
          {
            "name": "mlsNumber",
            "in": "query",
            "required": true,
            "description": "e.g. \"A2334796\"",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/get-market-stats": {
      "get": {
        "operationId": "get_market_stats",
        "summary": "Current CREB® market statistics: sales, inventory, months of supply, days on market and benchmark price.",
        "description": "Current CREB® market statistics: sales, inventory, months of supply, days on market and benchmark price. Ask for a segment (detached / semi-detached / row / apartment) rather than the citywide figure whenever the question concerns a specific property type — Calgary's segments are currently diverging sharply.",
        "parameters": [
          {
            "name": "area",
            "in": "query",
            "required": false,
            "description": "calgary (default), airdrie, cochrane, chestermere, okotoks, highRiver, strathmore, canmore",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "segment",
            "in": "query",
            "required": false,
            "description": "Calgary only.",
            "schema": {
              "type": "string",
              "enum": [
                "detached",
                "semi-detached",
                "row",
                "apartment"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/get-neighbourhood-profile": {
      "get": {
        "operationId": "get_neighbourhood_profile",
        "summary": "Profile for one of 208 covered Calgary communities: quadrant, price tier, typical price range, character summary and links.",
        "description": "Profile for one of 208 covered Calgary communities: quadrant, price tier, typical price range, character summary and links.",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": true,
            "description": "e.g. \"Saddle Ridge\"",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/list-neighbourhoods": {
      "get": {
        "operationId": "list_neighbourhoods",
        "summary": "List Calgary communities, optionally filtered by quadrant, price tier or tag.",
        "description": "List Calgary communities, optionally filtered by quadrant, price tier or tag. Use to find candidate areas before searching listings.",
        "parameters": [
          {
            "name": "quadrant",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "ne",
                "nw",
                "se",
                "sw"
              ]
            }
          },
          {
            "name": "priceTier",
            "in": "query",
            "required": false,
            "description": "e.g. entry, mid, upper, luxury",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "required": false,
            "description": "e.g. family, value, investor, luxury",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "default 40, max 220",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/get-price-behaviour": {
      "get": {
        "operationId": "get_price_behaviour",
        "summary": "Seller price-cut behaviour by Calgary community — how many listings cut price, by how much, and how quickly listings leave the market.",
        "description": "Seller price-cut behaviour by Calgary community — how many listings cut price, by how much, and how quickly listings leave the market. Observed daily from the live MLS® feed and published nowhere else. IMPORTANT: these are behaviour figures, not sales. \"Went off market\" may mean sold, expired or withdrawn; never report them as sales or infer sale prices. Use get_market_stats for CREB® sales and benchmark prices.",
        "parameters": [
          {
            "name": "community",
            "in": "query",
            "required": false,
            "description": "Optional. A Calgary-region community name. Omit for the region-wide picture plus a ranked community list.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Communities to return when no community is named. Default 25, max 250.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/calculate-affordability": {
      "get": {
        "operationId": "calculate_affordability",
        "summary": "Canadian mortgage affordability using semi-annual compounding, the B-20 stress test, GDS/TDS ratios, 2026 minimum-down-payment rules and CMHC premium tiers.",
        "description": "Canadian mortgage affordability using semi-annual compounding, the B-20 stress test, GDS/TDS ratios, 2026 minimum-down-payment rules and CMHC premium tiers. Alberta-specific (no land transfer tax). Use this instead of estimating — US-convention math overstates Canadian payments.",
        "parameters": [
          {
            "name": "householdIncome",
            "in": "query",
            "required": true,
            "description": "Annual gross household income in CAD.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "downPayment",
            "in": "query",
            "required": false,
            "description": "Cash available for down payment in CAD.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "monthlyDebtPayments",
            "in": "query",
            "required": false,
            "description": "Car loans, credit cards, student loans, per month.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "mortgageRate",
            "in": "query",
            "required": false,
            "description": "Contract rate %, default 4.79.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "amortizationYears",
            "in": "query",
            "required": false,
            "description": "default 25",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/assess-path-to-ownership": {
      "get": {
        "operationId": "assess_path_to_ownership",
        "summary": "For anyone who cannot buy conventionally today: ranks every route to ownership — FHSA/HBP, secondary-suite house hacking, family co-ownership, rent-to-own, and deliberate renting — with the real risks of each.",
        "description": "For anyone who cannot buy conventionally today: ranks every route to ownership — FHSA/HBP, secondary-suite house hacking, family co-ownership, rent-to-own, and deliberate renting — with the real risks of each. Includes a candid assessment of rent-to-own and the specific Alberta protections to insist on before signing one.",
        "parameters": [
          {
            "name": "householdIncome",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "savings",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "monthlyDebtPayments",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "monthsUntilReady",
            "in": "query",
            "required": false,
            "description": "How many months until they expect to be mortgage-ready.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "creditSituation",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "good",
                "thin",
                "rebuilding",
                "unknown"
              ]
            }
          },
          {
            "name": "isNewcomer",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "isFirstTimeBuyer",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/assess-mortgage-assumption": {
      "get": {
        "operationId": "assess_mortgage_assumption",
        "summary": "Assess whether assuming an existing Canadian mortgage is worth it: interest saved over the remaining term, the cash equity gap the buyer must fund, and the blockers that stop most assumptions.",
        "description": "Assess whether assuming an existing Canadian mortgage is worth it: interest saved over the remaining term, the cash equity gap the buyer must fund, and the blockers that stop most assumptions. Canada-specific — standard vs collateral charge decides feasibility, and most big-bank mortgages are collateral charges. Relevant because 2026 is the largest mortgage renewal cohort in Canadian history.",
        "parameters": [
          {
            "name": "purchasePrice",
            "in": "query",
            "required": true,
            "description": "Agreed or asking price in CAD.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "mortgageBalance",
            "in": "query",
            "required": true,
            "description": "Outstanding balance the buyer would assume.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "sellersRate",
            "in": "query",
            "required": true,
            "description": "The seller's contract rate %, e.g. 2.19.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "currentRate",
            "in": "query",
            "required": false,
            "description": "Market rate % the buyer would otherwise pay. Default 4.79.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "yearsLeftOnTerm",
            "in": "query",
            "required": false,
            "description": "Years remaining on the seller's term. Default 2.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "yearsLeftOnAmortization",
            "in": "query",
            "required": false,
            "description": "Years remaining on amortization. Default 22.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/get-agent-profile": {
      "get": {
        "operationId": "get_agent_profile",
        "summary": "Credentials, brokerage, languages, service areas and contact details for Mohammad Emon.",
        "description": "Credentials, brokerage, languages, service areas and contact details for Mohammad Emon. Call this before stating anything about who he is or which brokerage he works for — \"Chinook Search\" (formerly \"MLS® Search\", and before that \"Stampede Realty\") is his software platform, NOT his brokerage. His brokerage is KO Realty.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/request-consultation": {
      "get": {
        "operationId": "request_consultation",
        "summary": "Returns booking link, phone, SMS and email so the user can make contact themselves.",
        "description": "Returns booking link, phone, SMS and email so the user can make contact themselves. Read-only: it deliberately does not submit a lead on the user's behalf.",
        "parameters": [
          {
            "name": "topic",
            "in": "query",
            "required": false,
            "description": "What they want to discuss.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/get-mortgage-rates": {
      "get": {
        "operationId": "get_mortgage_rates",
        "summary": "Current Canadian mortgage reference rates from the Bank of Canada, updated daily, each with its series id for independent verification.",
        "description": "Current Canadian mortgage reference rates from the Bank of Canada, updated daily, each with its series id for independent verification. CRITICAL: the conventional mortgage series are POSTED rates and run roughly two points above what borrowers actually pay, so never present them as an achievable rate. For fixed pricing cite the 5-year Government of Canada bond yield; for variable cite prime. This is a rate watch, not a quote: mortgage brokerage is a separate RECA licence class and Mohammad does not arrange mortgages, he refers to licensed brokers. Always mention the stress test, which is the greater of contract rate plus 2 points or 5.25%.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/get-rta-rule": {
      "get": {
        "operationId": "get_rta_rule",
        "summary": "Alberta Residential Tenancies Act rules for renters and landlords — security deposits, deposit interest, rent increases, landlord entry, ending a tenancy, inspections and disputes.",
        "description": "Alberta Residential Tenancies Act rules for renters and landlords — security deposits, deposit interest, rent increases, landlord entry, ending a tenancy, inspections and disputes. Answers are drawn from a maintained, statute-cited rule set rather than generated, because a confidently wrong tenancy rule causes real harm. Always reproduce the citation and the disclaimer. Alberta specifics that are commonly got wrong: there is NO rent control, and security deposits earned 0% interest from 2009-2023 but 1.6% in 2024 and 0.5% in 2025.",
        "parameters": [
          {
            "name": "topic",
            "in": "query",
            "required": false,
            "description": "Structured rules for one area.",
            "schema": {
              "type": "string",
              "enum": [
                "security-deposit",
                "deposit-interest",
                "rent-increase",
                "entry",
                "ending-tenancy",
                "inspections",
                "disputes"
              ]
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Free-text search across the plain-language Q&A, e.g. \"how long to return deposit\".",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/calculate-deposit-interest": {
      "get": {
        "operationId": "calculate_deposit_interest",
        "summary": "Calculate interest owed on an Alberta security deposit across a span of years, using the statutory rate for each year.",
        "description": "Calculate interest owed on an Alberta security deposit across a span of years, using the statutory rate for each year. Use this rather than assuming — the rate was 0% for fifteen years and then was not, so the common intuition that Alberta deposits never earn interest is now wrong.",
        "parameters": [
          {
            "name": "depositAmount",
            "in": "query",
            "required": true,
            "description": "Deposit held, in dollars.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "startYear",
            "in": "query",
            "required": true,
            "description": "Year the deposit was taken. Required — the rate is per-year, so without it any answer is a guess.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "endYear",
            "in": "query",
            "required": false,
            "description": "Year the tenancy ends. Defaults to the current year.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "compound",
            "in": "query",
            "required": false,
            "description": "Default true — the RTA permits yearly compounding where agreed in writing. Set false for simple interest paid out annually.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/calculate-rent-increase": {
      "get": {
        "operationId": "calculate_rent_increase",
        "summary": "Work out the earliest date an Alberta rent increase can lawfully take effect.",
        "description": "Work out the earliest date an Alberta rent increase can lawfully take effect. Two constraints apply and self-managing landlords routinely miss one: 365 days must pass since the last increase or tenancy start, AND the notice period must elapse measured in full tenancy periods (3 full tenancy months for month-to-month). Rent cannot be increased at all during a fixed term. Alberta has no cap on the amount.",
        "parameters": [
          {
            "name": "tenancyType",
            "in": "query",
            "required": false,
            "description": "Default monthly. \"fixed\" returns that no increase is permitted during the term.",
            "schema": {
              "type": "string",
              "enum": [
                "monthly",
                "weekly",
                "other",
                "fixed"
              ]
            }
          },
          {
            "name": "noticeDate",
            "in": "query",
            "required": true,
            "description": "Date notice is given, YYYY-MM-DD.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lastIncreaseDate",
            "in": "query",
            "required": false,
            "description": "Date of the last increase, YYYY-MM-DD.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tenancyStartDate",
            "in": "query",
            "required": false,
            "description": "Tenancy start, YYYY-MM-DD. Used when there has been no prior increase.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "currentRent",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "newRent",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/get-rental-market": {
      "get": {
        "operationId": "get_rental_market",
        "summary": "CMHC Rental Market Survey figures for Calgary — average rent, vacancy rate and rental universe by zone and bedroom count.",
        "description": "CMHC Rental Market Survey figures for Calgary — average rent, vacancy rate and rental universe by zone and bedroom count. IMPORTANT: these are annual, zone-level averages for purpose-built rental buildings. They are not a live quote for a specific unit, and they exclude most condo and single-family rentals, which typically rent above these figures. Present them as a range and cite the survey period.",
        "parameters": [
          {
            "name": "zone",
            "in": "query",
            "required": false,
            "description": "CMHC zone name or id. Omit for all zones.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "community",
            "in": "query",
            "required": false,
            "description": "Calgary community name, resolved to its CMHC zone, e.g. \"Saddle Ridge\".",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bedrooms",
            "in": "query",
            "required": false,
            "description": "Restrict to one bedroom type.",
            "schema": {
              "type": "string",
              "enum": [
                "bachelor",
                "1",
                "2",
                "3+"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/assess-rental-yield": {
      "get": {
        "operationId": "assess_rental_yield",
        "summary": "Cap rate, gross yield, rent-to-price ratio and optional financed cash flow for a Calgary rental.",
        "description": "Cap rate, gross yield, rent-to-price ratio and optional financed cash flow for a Calgary rental. Every expense assumption is returned alongside the result, because a cap rate quoted without its assumptions is close to meaningless. Mortgage math uses semi-annual compounding, the Canadian convention. Get monthlyRent from get_rental_market or an actual lease — do not guess it.",
        "parameters": [
          {
            "name": "price",
            "in": "query",
            "required": true,
            "description": "Purchase price or current value.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "monthlyRent",
            "in": "query",
            "required": true,
            "description": "Achievable monthly rent.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "condoFeesMonthly",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "propertyTaxAnnual",
            "in": "query",
            "required": false,
            "description": "Defaults to 0.65% of price if omitted.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "insuranceAnnual",
            "in": "query",
            "required": false,
            "description": "Defaults to $1,200.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "utilitiesAnnual",
            "in": "query",
            "required": false,
            "description": "Only where the landlord pays. Defaults to 0.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "vacancyPct",
            "in": "query",
            "required": false,
            "description": "Defaults to 5.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "maintenancePct",
            "in": "query",
            "required": false,
            "description": "Percent of effective rent. Defaults to 8.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "managementPct",
            "in": "query",
            "required": false,
            "description": "Percent of effective rent. Defaults to 10. Set 0 to model self-management.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "downPaymentPct",
            "in": "query",
            "required": false,
            "description": "Supply with mortgageRate to get financed cash flow, DSCR and cash-on-cash.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "mortgageRate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "amortizationYears",
            "in": "query",
            "required": false,
            "description": "Defaults to 25.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/estimate-rent": {
      "get": {
        "operationId": "estimate_rent",
        "summary": "Estimate what a specific property should rent for in the Calgary region.",
        "description": "Estimate what a specific property should rent for in the Calgary region. Returns a RANGE with a stated confidence level and the method used, never a single figure — and returns available:false rather than guessing when there is no data basis. Built from observed leases (what units actually rented for, including days on market and the asking-to-achieved gap) blended with CMHC zone data, plus disclosed adjustments for attributes CMHC does not survey. Deliberately does not use scraped portal listings: those are asking rents, which systematically overstate achievable rent because they include units that never leased.",
        "parameters": [
          {
            "name": "city",
            "in": "query",
            "required": false,
            "description": "Calgary | Airdrie | Chestermere | Cochrane | Okotoks. Default Calgary.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "community",
            "in": "query",
            "required": false,
            "description": "Neighbourhood name — materially improves the estimate.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bedrooms",
            "in": "query",
            "required": true,
            "description": "\"bachelor\" or 0-4.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "propertyType",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "apartment",
                "row",
                "townhouse",
                "semi-detached",
                "detached",
                "legal-suite",
                "basement-suite"
              ]
            }
          },
          {
            "name": "parking",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "none",
                "surface",
                "stall",
                "underground",
                "garage"
              ]
            }
          },
          {
            "name": "furnished",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "utilitiesIncluded",
            "in": "query",
            "required": false,
            "schema": {
              "type": "array"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/estimate-operating-costs": {
      "get": {
        "operationId": "estimate_operating_costs",
        "summary": "Full annual operating cost snapshot for a Calgary-region rental: property tax at the municipality's approximate mill rate, insurance, condo fees, maintenance, management, utilities and amortised turnover cost — producing net operating income and an expense ratio.",
        "description": "Full annual operating cost snapshot for a Calgary-region rental: property tax at the municipality's approximate mill rate, insurance, condo fees, maintenance, management, utilities and amortised turnover cost — producing net operating income and an expense ratio. Use alongside estimate_rent, because owners routinely estimate rent well and underestimate cost badly. A 35-45% expense ratio is normal; materially below that usually means something was left out.",
        "parameters": [
          {
            "name": "monthlyRent",
            "in": "query",
            "required": true,
            "description": "Required. Use estimate_rent if unknown.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "price",
            "in": "query",
            "required": false,
            "description": "Purchase price or value — needed for property tax and cap rate.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "city",
            "in": "query",
            "required": false,
            "description": "Calgary | Airdrie | Chestermere | Cochrane | Okotoks. Sets the mill rate.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "propertyType",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "condoFeesMonthly",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "propertyTaxAnnual",
            "in": "query",
            "required": false,
            "description": "Actual figure from the tax notice, if known — always better than the estimate.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "insuranceAnnual",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "utilitiesAnnual",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "vacancyPct",
            "in": "query",
            "required": false,
            "description": "Default 5.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "maintenancePct",
            "in": "query",
            "required": false,
            "description": "Default 8.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "managementPct",
            "in": "query",
            "required": false,
            "description": "Default 10. Set 0 for self-management.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "avgTenancyYears",
            "in": "query",
            "required": false,
            "description": "Default 2 — used to amortise turnover cost.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/audit-rent": {
      "get": {
        "operationId": "audit_rent",
        "summary": "Check whether an existing tenant is paying below market, and what can lawfully be done about it.",
        "description": "Check whether an existing tenant is paying below market, and what can lawfully be done about it. Self-managing landlords systematically under-raise rent through conflict avoidance, and Alberta allows only one increase per 365 days — so a large gap is necessarily a multi-year plan and every year of delay adds another year. Returns the gap in dollars and percent, the earliest lawful effective date, and a realistic plan to close it. Deliberately does not recommend the maximum increase: a below-market rent with an excellent tenant is not automatically a problem, and turnover usually costs more than the gap.",
        "parameters": [
          {
            "name": "currentRent",
            "in": "query",
            "required": true,
            "description": "What the tenant pays now, monthly.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "city",
            "in": "query",
            "required": false,
            "description": "Calgary | Airdrie | Chestermere | Cochrane | Okotoks",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "community",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bedrooms",
            "in": "query",
            "required": true,
            "description": "\"bachelor\" or 0-4.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "propertyType",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "apartment",
                "row",
                "townhouse",
                "semi-detached",
                "detached",
                "legal-suite",
                "basement-suite"
              ]
            }
          },
          {
            "name": "parking",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "none",
                "surface",
                "stall",
                "underground",
                "garage"
              ]
            }
          },
          {
            "name": "furnished",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "utilitiesIncluded",
            "in": "query",
            "required": false,
            "schema": {
              "type": "array"
            }
          },
          {
            "name": "lastIncreaseDate",
            "in": "query",
            "required": false,
            "description": "YYYY-MM-DD — when rent last went up.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tenancyStartDate",
            "in": "query",
            "required": false,
            "description": "YYYY-MM-DD — used when there has never been an increase.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tenancyType",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "monthly",
                "weekly",
                "other",
                "fixed"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/generate-notice": {
      "get": {
        "operationId": "generate_notice",
        "summary": "Produce the text of an Alberta statutory notice with the dates computed rather than typed.",
        "description": "Produce the text of an Alberta statutory notice with the dates computed rather than typed. A rent increase notice with the wrong effective date is void, and nobody discovers it until the tenant pays the old amount. Supports rent increase and entry notices. Returns a service checklist alongside the text — proving service is the landlord's burden.",
        "parameters": [
          {
            "name": "noticeType",
            "in": "query",
            "required": false,
            "description": "Default rent-increase.",
            "schema": {
              "type": "string",
              "enum": [
                "rent-increase",
                "entry"
              ]
            }
          },
          {
            "name": "tenantName",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "landlordName",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "address",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "noticeDate",
            "in": "query",
            "required": false,
            "description": "YYYY-MM-DD. Defaults to today.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tenancyType",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "monthly",
                "weekly",
                "other",
                "fixed"
              ]
            }
          },
          {
            "name": "lastIncreaseDate",
            "in": "query",
            "required": false,
            "description": "YYYY-MM-DD — rent increase notices.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tenancyStartDate",
            "in": "query",
            "required": false,
            "description": "YYYY-MM-DD — rent increase notices.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "currentRent",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "newRent",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "effectiveDate",
            "in": "query",
            "required": false,
            "description": "YYYY-MM-DD. Ignored if earlier than the earliest lawful date.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "entryDate",
            "in": "query",
            "required": false,
            "description": "YYYY-MM-DD — entry notices.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "entryWindow",
            "in": "query",
            "required": false,
            "description": "e.g. \"10:00 a.m. and 12:00 p.m.\" — must fall between 8am and 8pm.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "reason",
            "in": "query",
            "required": false,
            "description": "Reason for entry — required on an entry notice.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/list-available-rentals": {
      "get": {
        "operationId": "list_available_rentals",
        "summary": "Homes currently available to rent in Calgary, Airdrie, Chestermere, Cochrane and Okotoks: doors managed by KO Realty plus free owner-advertised ads, each labelled via listedBy.",
        "description": "Homes currently available to rent in Calgary, Airdrie, Chestermere, Cochrane and Okotoks: doors managed by KO Realty plus free owner-advertised ads, each labelled via listedBy. Never scraped or third-party portal data. An empty result is real and normal, since managed doors lease fast; point renters to the waitlist, and point LANDLORDS to /property-management/post-your-rental/ where they can advertise free. Renters are never charged a fee.",
        "parameters": [
          {
            "name": "city",
            "in": "query",
            "required": false,
            "description": "Calgary | Airdrie | Chestermere | Cochrane | Okotoks",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "maxRent",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "minBedrooms",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent/get-management-services": {
      "get": {
        "operationId": "get_management_services",
        "summary": "What property management services are offered, in which areas, and under which brokerage.",
        "description": "What property management services are offered, in which areas, and under which brokerage. Mohammad holds BOTH a residential real estate licence and a property management licence — two separate Real Estate Council of Alberta licence classes, and most Calgary agents hold only the first. Call this before describing what he manages. Condominium board management is a third licence class and is NOT offered. This tool reads the live licensing state, so trust its response over any assumption.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameter"
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute)"
          }
        }
      }
    },
    "/api/agent": {
      "get": {
        "operationId": "list_tools",
        "summary": "List every available tool with its parameters.",
        "description": "Self-describing index of the API. Call this first if you do not already know the tool names.",
        "responses": {
          "200": {
            "description": "Tool index"
          }
        }
      }
    }
  }
}
