Koskamo

Cashew MCP

An official MCP server by Cashew, Singapore's digital mortgage platform, providing read-only access to live bank mortgage rates, personalized package recommendations, and a comprehensive suite of mortgage calculators for purchases, refinancing, and affordability analysis.

Endpoint and access

No sign-in to connect and list tools. Individual tools may still need a vendor account, credentials, or permissions; check the documented prerequisites.

Cashew MCP logo, by CashewVendor evidenceStreamable HTTPNo sign-inEndpoint checked 2026-11-01Operated by Cashew
MCP endpoint (Streamable HTTP)
https://www.cashew.sg/mcp
Vendor documentation

What you can do with it

  • Retrieve current mortgage rates from Singapore banks
  • Get personalized home loan package recommendations
  • Calculate monthly mortgage repayments and affordability
  • Estimate stamp duties and refinancing savings

Source and maintenance

Vendor evidenceRegistry 1.1.0
Maintenance checked

Protocol and capabilities

Server
sg.cashew/mcp 1.1.0
MCP protocol
2025-06-18
Tools
23 advertised
Resources
Not advertised
Resource templates
Not advertised
Prompts
Not advertised
Server-advertised capabilities
{
  "tools": {
    "listChanged": true
  }
}

Server-provided instructions

Untrusted server output shown for inspection. It is not Koskamo guidance and is excluded from knowledge search.

This server is stateless and does not remember prior calls. A recommendation is only valid for the exact inputs of the request that produced it. If the borrower changes ANY input or preference — deal kind, current bank, property type/status, loan amount, rate-type preference, or any free-text priority — you MUST call `recommend_packages` again with the full, updated input set. Never adjust, re-rank, or re-explain a previous response from memory; always issue a fresh call. Before calling `recommend_packages`, gather the core deal facts and ask for any that are missing rather than guessing: whether it is a PURCHASE or REFINANCE; for a REFINANCE, the current bank (resolve the lender name to `currentBankId` via `list_banks`); the property type; the property status; and the loan amount. Two inputs are optional and only sharpen the ranking: a FIXED vs FLOATING rate preference (`ratesType`), and a free-text summary of the borrower's priorities (`userContext`). Conventions across all tools: every monetary amount is in Singapore dollars (SGD). Interest rates that you PASS IN to the calculators are whole-number percentages (e.g. 3.5 means 3.5% p.a., NOT 0.035). Rates RETURNED by `calculate_package_schedule` and `get_index_quotes` are decimal fractions (0.0356 = 3.56%) instead. Each field's description states which. Every tool is read-only and has no side effects beyond optional document generation, and the server keeps no state between calls.

Tools

23 advertised

recommend_packages — Recommend mortgage packages
Advertised by the server during the last successful anonymous check.
Advertised read-only

Rank Singapore mortgage packages for a borrower. Returns `recommended` (up to 3 picks, each with a package id and `reasoning`; ranked by the LLM when `userContext` is given, otherwise the cheapest package of each rate structure), `lowestRateIds` (up to 3 package ids, cheapest eligible first), `isPersonalised` (whether `userContext` shaped the ranking), and `packages`, a lookup of full package details for every id referenced. Core inputs (gather these first; ask the borrower if missing — do not guess): `dealKind` (PURCHASE | REFINANCE); `currentBankId` (required only for REFINANCE — use `list_banks` to resolve the lender name into its UUID); `propertyType` (HDB | PRIVATE | COMMERCIAL); `propertyStatus`; `loanAmount`. `propertyStatus` depends on `propertyType` — only these pairings are valid: HDB → COMPLETED (resale) or BTO (Build-To-Order, new); PRIVATE → COMPLETED (ready-built) or BUC (Built-Under-Construction, new); COMMERCIAL → OWNEROCCUPIED, INVESTMENT or PERSONAL. Do not mix across types (e.g. HDB + BUC or PRIVATE + BTO are invalid). Optional inputs (only refine the ranking): `ratesType` (FIXED | FLOATING) and `userContext` (free-text borrower priorities, timeline, bank relationships, citizenship, etc.). Pass `formats: ["pdf"]` to also generate a one-page comparison document of the recommended set (with per-package verdicts); the download URL comes back under `documents`. Optional `tenureYears` (default 25) sets the document's instalment assumptions. Stateless: if any input or preference changes, call this tool again with the full updated set — never infer an updated answer from a previous response.

recommend_packages input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "propertyType",
    "propertyStatus",
    "dealKind",
    "loanAmount"
  ],
  "properties": {
    "formats": {
      "type": "array",
      "items": {
        "enum": [
          "pdf"
        ],
        "type": "string"
      },
      "description": "Document formats to generate for the recommended set (currently only \"pdf\"); the download URL is returned under `documents`. Omit/empty for data only."
    },
    "dealKind": {
      "enum": [
        "PURCHASE",
        "REFINANCE"
      ],
      "type": "string",
      "description": "PURCHASE (new loan) or REFINANCE (switching lender)."
    },
    "ratesType": {
      "enum": [
        "FIXED",
        "FLOATING"
      ],
      "type": "string",
      "description": "Optional rate type: FIXED or FLOATING. This is a hard filter on the candidate pool, not a ranking hint. Omit it when the borrower stated no preference, so a mix of rate types stays eligible."
    },
    "loanAmount": {
      "type": "integer",
      "maximum": 9007199254740991,
      "minimum": 0,
      "description": "Amount the borrower wants to borrow, in SGD. Packages whose loan-size range does not cover it are excluded."
    },
    "tenureYears": {
      "type": "integer",
      "maximum": 35,
      "minimum": 1,
      "description": "Repayment period, in years, for the instalments shown in the comparison document; only used with `formats`. 25 when omitted."
    },
    "userContext": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 2000
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Optional free-text summary of the borrower's priorities, timeline, bank relationships, citizenship, employment, family changes, etc. Only refines the LLM ranking; omit if there are no stated priorities."
    },
    "propertyType": {
      "enum": [
        "HDB",
        "PRIVATE",
        "COMMERCIAL"
      ],
      "type": "string",
      "description": "The kind of property securing the loan: an HDB flat, a private home or a commercial property. Only packages offered for it are considered, and it decides which `propertyStatus` values are valid."
    },
    "currentBankId": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
      "description": "The borrower's current lender, as the bank `id` returned by `list_banks`. Needed for REFINANCE deals, where that bank's packages are left out; ignored for PURCHASE."
    },
    "candidateLimit": {
      "type": "integer",
      "default": 60,
      "maximum": 60,
      "minimum": 1,
      "description": "How many candidate packages the ranker weighs for each of its slots (floating, 2-year fixed, longest fixed); values above 8 have no further effect. Leave unset unless deliberately narrowing the choice."
    },
    "propertyStatus": {
      "enum": [
        "BTO",
        "OWNEROCCUPIED",
        "INVESTMENT",
        "PERSONAL",
        "COMPLETED",
        "BUC"
      ],
      "type": "string",
      "description": "Stage or use of the property, which must suit `propertyType`. For HDB: COMPLETED for a resale flat, BTO for a new Build-To-Order flat. For PRIVATE: COMPLETED for a ready-built home, BUC for one still under construction. For COMMERCIAL: OWNEROCCUPIED, INVESTMENT or PERSONAL."
    },
    "lockInPeriodYrs": {
      "type": "integer",
      "maximum": 5,
      "minimum": 0,
      "description": "Optional filter: the longest lock-in, in years, the borrower will accept. Packages with a longer lock-in are excluded; packages without one always qualify."
    },
    "fixedDurationYrs": {
      "anyOf": [
        {
          "type": "integer",
          "maximum": 5,
          "minimum": 1
        },
        {
          "type": "array",
          "items": {
            "type": "integer",
            "maximum": 5,
            "minimum": 1
          },
          "minItems": 1
        }
      ],
      "description": "Optional filter: keep only packages whose fixed-rate period lasts this many years. Pass an array to accept any of several. Only applied when `ratesType` is FIXED."
    }
  }
}
recommend_packages output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "packages",
    "recommended",
    "lowestRateIds",
    "isPersonalised"
  ],
  "properties": {
    "packages": {
      "type": "object",
      "description": "Lookup of full package details keyed by package id. Every id referenced in `recommended` and `lowestRateIds` has an entry here.",
      "propertyNames": {
        "type": "string",
        "format": "uuid",
        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
      },
      "additionalProperties": {
        "type": "object",
        "required": [
          "id",
          "createdAt",
          "updatedAt",
          "bankId",
          "maskIdentity",
          "propertyType",
          "propertyStatus",
          "dealKinds",
          "status",
          "ratesType",
          "minSize",
          "maxSize",
          "fixedDurationYrs",
          "lockInPeriodYrs",
          "freeConversion",
          "partialRedemption",
          "fullRedemption",
          "redemptionComment",
          "waiversDueToSale",
          "cancellation",
          "specialTerms",
          "validFrom",
          "validUntil",
          "effectiveYear1",
          "effectiveYear2",
          "effectiveYear3",
          "effectiveYear4",
          "effectiveYear5",
          "effectiveThereafter",
          "effectiveAverageRate",
          "effectiveAt",
          "importHash"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "bank": {
            "type": "object",
            "required": [
              "id",
              "name",
              "slug",
              "logoUrl",
              "website",
              "maskIdentity"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              "name": {
                "type": "string"
              },
              "slug": {
                "type": "string"
              },
              "masked": {
                "type": "boolean"
              },
              "logoUrl": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "website": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "maskIdentity": {
                "type": "boolean",
                "default": false
              }
            },
            "additionalProperties": false
          },
          "bankId": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "source": {
            "enum": [
              "SHEET",
              "MANUAL"
            ],
            "type": "string"
          },
          "status": {
            "enum": [
              "ACTIVE",
              "INACTIVE"
            ],
            "type": "string"
          },
          "maxSize": {
            "type": "integer",
            "maximum": 9007199254740991,
            "minimum": -9007199254740991
          },
          "minSize": {
            "type": "integer",
            "maximum": 9007199254740991,
            "minimum": -9007199254740991
          },
          "rateLegs": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "createdAt",
                "updatedAt",
                "packageId",
                "sequence",
                "durationMonths",
                "legType",
                "fixedPct",
                "spreadPct",
                "indexId"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                },
                "index": {
                  "anyOf": [
                    {
                      "type": "object",
                      "required": [
                        "id",
                        "slug",
                        "name",
                        "currency"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "name": {
                          "type": "string"
                        },
                        "slug": {
                          "type": "string"
                        },
                        "currency": {
                          "type": "string"
                        }
                      },
                      "additionalProperties": false
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "indexId": {
                  "anyOf": [
                    {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "legType": {
                  "enum": [
                    "FIXED",
                    "FLOATING"
                  ],
                  "type": "string"
                },
                "fixedPct": {
                  "anyOf": [
                    {
                      "type": "number"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "sequence": {
                  "type": "integer",
                  "maximum": 9007199254740991,
                  "minimum": -9007199254740991
                },
                "createdAt": {
                  "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))$"
                },
                "packageId": {
                  "type": "string",
                  "format": "uuid",
                  "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                },
                "spreadPct": {
                  "anyOf": [
                    {
                      "type": "number"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "updatedAt": {
                  "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))$"
                },
                "latestQuote": {
                  "anyOf": [
                    {
                      "type": "object",
                      "required": [
                        "id",
                        "marketIndexId",
                        "asOfDate",
                        "rate"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "rate": {
                          "type": "number"
                        },
                        "asOfDate": {
                          "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))$"
                        },
                        "marketIndexId": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        }
                      },
                      "additionalProperties": false
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "computedRate": {
                  "anyOf": [
                    {
                      "type": "number"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "durationMonths": {
                  "anyOf": [
                    {
                      "type": "integer",
                      "maximum": 9007199254740991,
                      "minimum": -9007199254740991
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "additionalProperties": false
            }
          },
          "createdAt": {
            "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))$"
          },
          "dealKinds": {
            "type": "array",
            "items": {
              "enum": [
                "PURCHASE",
                "REFINANCE"
              ],
              "type": "string"
            }
          },
          "ratesType": {
            "enum": [
              "FIXED",
              "FLOATING"
            ],
            "type": "string"
          },
          "updatedAt": {
            "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))$"
          },
          "validFrom": {
            "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))$"
          },
          "importHash": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "validUntil": {
            "anyOf": [
              {
                "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))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "effectiveAt": {
            "anyOf": [
              {
                "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))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "cancellation": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "maskIdentity": {
            "type": "boolean",
            "default": false
          },
          "propertyType": {
            "enum": [
              "HDB",
              "PRIVATE",
              "COMMERCIAL"
            ],
            "type": "string"
          },
          "specialTerms": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "effectiveYear1": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "effectiveYear2": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "effectiveYear3": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "effectiveYear4": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "effectiveYear5": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "freeConversion": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "fullRedemption": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "propertyStatus": {
            "anyOf": [
              {
                "enum": [
                  "BTO",
                  "OWNEROCCUPIED",
                  "INVESTMENT",
                  "PERSONAL",
                  "COMPLETED",
                  "BUC"
                ],
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "lockInPeriodYrs": {
            "anyOf": [
              {
                "type": "integer",
                "maximum": 9007199254740991,
                "minimum": -9007199254740991
              },
              {
                "type": "null"
              }
            ]
          },
          "fixedDurationYrs": {
            "anyOf": [
              {
                "type": "integer",
                "maximum": 9007199254740991,
                "minimum": -9007199254740991
              },
              {
                "type": "null"
              }
            ]
          },
          "waiversDueToSale": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "partialRedemption": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "redemptionComment": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "effectiveThereafter": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "effectiveAverageRate": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "additionalProperties": false
      }
    },
    "documents": {
      "type": "object",
      "required": [
        "pdfUrl"
      ],
      "properties": {
        "pdfUrl": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "description": "Public PDF download URL, or null if not generated."
        }
      },
      "description": "Comparison document for the recommended set; only present when `formats` was requested.",
      "additionalProperties": false
    },
    "recommended": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "rank",
          "packageId",
          "reasoning"
        ],
        "properties": {
          "rank": {
            "type": "integer",
            "maximum": 3,
            "minimum": 1,
            "description": "1-based rank (1 = best fit for this borrower)."
          },
          "packageId": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Id of the ranked package; look it up in `packages`."
          },
          "reasoning": {
            "type": "string",
            "description": "Short LLM rationale for why this package fits the borrower."
          }
        },
        "additionalProperties": false
      },
      "maxItems": 3,
      "description": "Up to 3 picks. Ranked by the LLM when `userContext` is given and ranking succeeds, otherwise the cheapest package of each structure; fewer when fewer candidates exist after filtering."
    },
    "lowestRateIds": {
      "type": "array",
      "items": {
        "type": "string",
        "format": "uuid",
        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
      },
      "maxItems": 3,
      "description": "Up to 3 package ids, cheapest first by effective average rate. A fixed package whose lock-in outlives its fixed period is ranked behind eligible ones and only included to fill the list."
    },
    "isPersonalised": {
      "type": "boolean",
      "description": "True when `userContext` was supplied and the ranking reflects it."
    }
  },
  "additionalProperties": false
}
recommend_packages annotations
{
  "title": "Recommend mortgage packages",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
compare_packages — Compare mortgage packages
Advertised by the server during the last successful anonymous check.
Advertised read-only

Compare 2–3 SPECIFIC mortgage packages side by side: average rate over the lock-in, benchmark index, effective rate and monthly instalment per year, interest paid per year, and flexibility terms (prepayment, free conversion, sale waiver, penalties, minimum loan). Pass `packageIds` from `recommend_packages`, plus `loanAmount` and optionally `tenureYears` (default 25). `dealKind`/`propertyType`/`propertyStatus` only label the report header. Pass `formats: ["pdf"]` to also render the one-page comparison document — the download URL comes back under `documents.pdfUrl` (null otherwise). This comparison has no verdict/recommendation section by default because it carries no borrower context; for a personalised ranking with verdicts, use `recommend_packages` (optionally with `formats`). The optional `verdicts`/`recommendedId` inputs only carry over the reasoning from a prior `recommend_packages` call — never author them.

compare_packages input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "packageIds",
    "loanAmount"
  ],
  "properties": {
    "formats": {
      "type": "array",
      "items": {
        "enum": [
          "pdf"
        ],
        "type": "string"
      },
      "description": "Document formats to generate and upload (currently only \"pdf\"); the download URL is returned under `documents`. Omit/empty for data only."
    },
    "dealKind": {
      "enum": [
        "PURCHASE",
        "REFINANCE"
      ],
      "type": "string",
      "description": "Whether the loan is a new PURCHASE or a REFINANCE from another lender. Only labels the report header; the comparison is unchanged."
    },
    "verdicts": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "packageId",
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "maxLength": 2000,
            "minLength": 1,
            "description": "Verdict text, copied verbatim from the matching `recommended[].reasoning`."
          },
          "packageId": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Package the verdict belongs to: one of `packageIds`, taken from `recommended[].packageId` of the prior `recommend_packages` call."
          }
        }
      },
      "maxItems": 3,
      "description": "Per-package verdict texts carried over from a prior `recommend_packages` call (the LLM reasoning). Adds the verdict row to the comparison; omit for a verdict-free report. Do not author these by hand."
    },
    "loanAmount": {
      "type": "integer",
      "maximum": 9007199254740991,
      "minimum": -9007199254740991,
      "description": "Loan principal in SGD (whole dollars)."
    },
    "packageIds": {
      "type": "array",
      "items": {
        "type": "string",
        "format": "uuid",
        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
      },
      "maxItems": 3,
      "minItems": 2,
      "description": "2–3 package ids to compare, e.g. from `recommend_packages`."
    },
    "tenureYears": {
      "type": "integer",
      "default": 25,
      "maximum": 35,
      "minimum": 1,
      "description": "Repayment period, in years, used for each package’s instalment and interest."
    },
    "propertyType": {
      "enum": [
        "HDB",
        "PRIVATE",
        "COMMERCIAL"
      ],
      "type": "string",
      "description": "The kind of property securing the loan (HDB flat, private home or commercial). Only labels the report header; the comparison is unchanged."
    },
    "recommendedId": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
      "description": "The rank-1 package from the prior `recommend_packages` call; its column is highlighted as \"Recommended\". Only meaningful together with `verdicts`."
    },
    "propertyStatus": {
      "enum": [
        "BTO",
        "OWNEROCCUPIED",
        "INVESTMENT",
        "PERSONAL",
        "COMPLETED",
        "BUC"
      ],
      "type": "string",
      "description": "Stage or use of the property, e.g. COMPLETED (resale or ready-built), BTO or BUC (new, under construction). Only labels the report header; the comparison is unchanged."
    },
    "maskBankIdentity": {
      "type": "boolean",
      "description": "When true and a bank has maskIdentity set, replace the bank name with \"Confidential Rate\" and hide the logo in the comparison report."
    }
  }
}
compare_packages output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "data",
    "documents"
  ],
  "properties": {
    "data": {
      "type": "object",
      "required": [
        "context",
        "columns"
      ],
      "properties": {
        "columns": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "packageId",
              "bankName",
              "bankLogoUrl",
              "bankMaskIdentity",
              "tag",
              "subtitle",
              "ratesType",
              "lockInYears",
              "avgRateLockIn",
              "benchmark",
              "verdict",
              "years",
              "interestByYear",
              "terms"
            ],
            "properties": {
              "tag": {
                "enum": [
                  "RECOMMENDED",
                  "ALTERNATIVE",
                  "OPTION"
                ],
                "type": "string",
                "description": "Column emphasis: RECOMMENDED is highlighted; OPTION is the neutral peer tag."
              },
              "terms": {
                "type": "object",
                "required": [
                  "partialPrepayment",
                  "freeConversion",
                  "saleWaiver",
                  "partialRedemptionPct",
                  "fullRedemptionPct",
                  "cancellationPct",
                  "minLoanAmount",
                  "specialTerms"
                ],
                "properties": {
                  "saleWaiver": {
                    "type": "object",
                    "required": [
                      "icon",
                      "label"
                    ],
                    "properties": {
                      "icon": {
                        "anyOf": [
                          {
                            "enum": [
                              "yes",
                              "partial",
                              "no"
                            ],
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "description": "Traffic-light marker; null renders without an icon."
                      },
                      "known": {
                        "type": "boolean",
                        "description": "False when the raw bank-sheet code was not recognised and `label` carries the raw value; absent means recognised."
                      },
                      "label": {
                        "type": "string",
                        "description": "Short display label, e.g. \"After lock-in\" or \"100%\"."
                      }
                    },
                    "additionalProperties": false
                  },
                  "specialTerms": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Free-text special terms/promotions for this package, or null when none."
                  },
                  "minLoanAmount": {
                    "type": "integer",
                    "maximum": 9007199254740991,
                    "minimum": -9007199254740991
                  },
                  "freeConversion": {
                    "type": "object",
                    "required": [
                      "icon",
                      "label"
                    ],
                    "properties": {
                      "icon": {
                        "anyOf": [
                          {
                            "enum": [
                              "yes",
                              "partial",
                              "no"
                            ],
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "description": "Traffic-light marker; null renders without an icon."
                      },
                      "known": {
                        "type": "boolean",
                        "description": "False when the raw bank-sheet code was not recognised and `label` carries the raw value; absent means recognised."
                      },
                      "label": {
                        "type": "string",
                        "description": "Short display label, e.g. \"After lock-in\" or \"100%\"."
                      }
                    },
                    "additionalProperties": false
                  },
                  "cancellationPct": {
                    "anyOf": [
                      {
                        "type": "number"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "fullRedemptionPct": {
                    "anyOf": [
                      {
                        "type": "number"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "partialPrepayment": {
                    "type": "object",
                    "required": [
                      "icon",
                      "label"
                    ],
                    "properties": {
                      "icon": {
                        "anyOf": [
                          {
                            "enum": [
                              "yes",
                              "partial",
                              "no"
                            ],
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "description": "Traffic-light marker; null renders without an icon."
                      },
                      "known": {
                        "type": "boolean",
                        "description": "False when the raw bank-sheet code was not recognised and `label` carries the raw value; absent means recognised."
                      },
                      "label": {
                        "type": "string",
                        "description": "Short display label, e.g. \"After lock-in\" or \"100%\"."
                      }
                    },
                    "additionalProperties": false
                  },
                  "partialRedemptionPct": {
                    "anyOf": [
                      {
                        "type": "number"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "additionalProperties": false
              },
              "years": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "yearLabel",
                    "rateLabel",
                    "rate",
                    "isFloating",
                    "monthlyPayment"
                  ],
                  "properties": {
                    "rate": {
                      "type": "number",
                      "description": "Effective annual rate, as a decimal fraction (0.035 = 3.5%)."
                    },
                    "rateLabel": {
                      "type": "string",
                      "description": "Rate description, e.g. \"Fixed\" or \"3M SORA + 1.00%\"."
                    },
                    "yearLabel": {
                      "type": "string",
                      "description": "\"Year 1\" … or \"Thereafter\"."
                    },
                    "isFloating": {
                      "type": "boolean",
                      "description": "True when the rate is benchmark-linked."
                    },
                    "monthlyPayment": {
                      "type": "number",
                      "description": "Monthly instalment during this year, in SGD."
                    }
                  },
                  "additionalProperties": false
                }
              },
              "verdict": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Per-package reasoning from the LLM ranking; null in verdict-free comparisons."
              },
              "bankName": {
                "type": "string"
              },
              "subtitle": {
                "type": "string",
                "description": "Short package descriptor, e.g. \"Fixed · 2-yr lock-in\"."
              },
              "benchmark": {
                "anyOf": [
                  {
                    "type": "object",
                    "required": [
                      "name",
                      "rate",
                      "asOfDate"
                    ],
                    "properties": {
                      "name": {
                        "type": "string",
                        "description": "Benchmark name, e.g. \"3M SORA\"."
                      },
                      "rate": {
                        "type": "number",
                        "description": "Latest benchmark rate, as a decimal fraction (0.0356 = 3.56% p.a.)."
                      },
                      "asOfDate": {
                        "type": "string",
                        "description": "Date of the benchmark quote (ISO date, YYYY-MM-DD)."
                      }
                    },
                    "additionalProperties": false
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Benchmark the first floating tier is priced off, or null for fully fixed packages."
              },
              "packageId": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              "ratesType": {
                "enum": [
                  "FIXED",
                  "FLOATING"
                ],
                "type": "string"
              },
              "bankLogoUrl": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Lender logo URL, or null."
              },
              "lockInYears": {
                "anyOf": [
                  {
                    "type": "integer",
                    "maximum": 9007199254740991,
                    "minimum": -9007199254740991
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "avgRateLockIn": {
                "anyOf": [
                  {
                    "type": "number"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Mean effective rate across the lock-in years (decimal fraction); falls back to the package's effective average rate when there is no lock-in."
              },
              "interestByYear": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "year",
                    "interestPaid"
                  ],
                  "properties": {
                    "year": {
                      "type": "integer",
                      "maximum": 9007199254740991,
                      "minimum": -9007199254740991
                    },
                    "interestPaid": {
                      "type": "number"
                    }
                  },
                  "additionalProperties": false
                },
                "description": "Interest paid per year (SGD) over the displayed lock-in years."
              },
              "bankMaskIdentity": {
                "type": "boolean",
                "default": false,
                "description": "True when the lender identity is masked."
              }
            },
            "additionalProperties": false
          },
          "maxItems": 3,
          "minItems": 2
        },
        "context": {
          "type": "object",
          "required": [
            "dealLabel",
            "propertyLabel",
            "statusLabel",
            "loanAmount",
            "tenureYears",
            "preparedLabel",
            "cornerLabel",
            "hasVerdicts"
          ],
          "properties": {
            "dealLabel": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ]
            },
            "loanAmount": {
              "type": "integer",
              "maximum": 9007199254740991,
              "minimum": -9007199254740991
            },
            "cornerLabel": {
              "type": "string",
              "description": "Table corner caption, e.g. \"Cashew top 3 · fixed\"."
            },
            "hasVerdicts": {
              "type": "boolean",
              "description": "True when any column carries a verdict."
            },
            "statusLabel": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ]
            },
            "tenureYears": {
              "type": "integer",
              "maximum": 9007199254740991,
              "minimum": -9007199254740991
            },
            "preparedLabel": {
              "type": "string",
              "description": "Report generation date, e.g. \"5 Jun 2026\"."
            },
            "propertyLabel": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ]
            }
          },
          "additionalProperties": false
        }
      },
      "description": "The assembled side-by-side comparison.",
      "additionalProperties": false
    },
    "documents": {
      "type": "object",
      "required": [
        "pdfUrl"
      ],
      "properties": {
        "pdfUrl": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "description": "Public PDF download URL, or null if not generated."
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}
compare_packages annotations
{
  "title": "Compare mortgage packages",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
list_banks — List banks
Advertised by the server during the last successful anonymous check.
Advertised read-only

List active banks (id + name), sorted by name. Use this to map a borrower's current lender name to the `currentBankId` UUID required by `recommend_packages` for refinance deals.

list_banks input schema
{
  "type": "object",
  "properties": {}
}
list_banks output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "banks"
  ],
  "properties": {
    "banks": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Bank id; pass it as `currentBankId` to `recommend_packages`."
          },
          "name": {
            "type": "string",
            "description": "Bank display name, e.g. \"DBS\" or \"OCBC\"."
          }
        },
        "additionalProperties": false
      },
      "description": "Active banks, sorted by name."
    }
  },
  "additionalProperties": false
}
list_banks annotations
{
  "title": "List banks",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
get_property_valuation — Get indicative property valuation
Advertised by the server during the last successful anonymous check.
Advertised read-onlyOpen-world

Indicative SPML valuation for a Singapore unit. Requires postal code and unit number in FF-SS format (e.g. "04-02"). propertyType ("Condo" or "HDB") and areaSqft are optional — when propertyType is omitted, both types are probed and the one that values the unit is used (`propertyTypeUsed` in the result tells you which). A `valued: false` result means SPML could not value the unit — that is a valid answer, not an error.

get_property_valuation input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "postalCode",
    "unitNumber"
  ],
  "properties": {
    "areaSqft": {
      "type": "number",
      "description": "Optional built-in/strata area in sqft; can rescue a \"$0\" valuation.",
      "exclusiveMinimum": 0
    },
    "postalCode": {
      "type": "string",
      "pattern": "^\\d{6}$",
      "description": "Postal code of the building the unit is in, e.g. \"248973\"."
    },
    "unitNumber": {
      "type": "string",
      "pattern": "^\\d{2,3}-\\d{1,4}$",
      "description": "Floor and stack of the unit, e.g. \"04-02\" for floor 4, stack 02."
    },
    "propertyType": {
      "enum": [
        "Condo",
        "HDB"
      ],
      "type": "string",
      "description": "Whether the unit is a private condominium or an HDB flat. Omit to auto-detect: both are probed and the one that values the unit is used (`propertyTypeUsed` reports which)."
    }
  }
}
get_property_valuation output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "valued",
    "valuation",
    "pricePerSqft",
    "pricePerSqm",
    "capitalGain",
    "requestedPropertyType",
    "propertyTypeUsed",
    "raw"
  ],
  "properties": {
    "raw": {
      "type": "object",
      "description": "Raw SPML response strings, for display/debugging.",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "valued": {
      "type": "boolean",
      "description": "True if SPML returned a valuation; false means the unit could not be valued."
    },
    "valuation": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "description": "Indicative market value in SGD, or null if unvalued."
    },
    "capitalGain": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "description": "Estimated capital gain since last transaction (SGD), or null."
    },
    "pricePerSqm": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "description": "Indicative price per square metre (SGD), or null."
    },
    "pricePerSqft": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "description": "Indicative price per square foot (SGD), or null."
    },
    "propertyTypeUsed": {
      "anyOf": [
        {
          "enum": [
            "Condo",
            "HDB"
          ],
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "description": "The property type that produced the valuation, or null if unvalued."
    },
    "requestedPropertyType": {
      "anyOf": [
        {
          "enum": [
            "Condo",
            "HDB"
          ],
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "description": "The property type the caller requested, or null if auto-detection was used."
    }
  },
  "additionalProperties": false
}
get_property_valuation annotations
{
  "title": "Get indicative property valuation",
  "readOnlyHint": true,
  "openWorldHint": true,
  "idempotentHint": true,
  "destructiveHint": false
}
calculate_package_schedule — Calculate package payment schedule
Advertised by the server during the last successful anonymous check.
Advertised read-only

Compute the amortisation schedule for a SPECIFIC mortgage package using that package's real effective rates. Use this once the borrower has picked a package from `recommend_packages`; for a hypothetical "what-if" rate the borrower does not yet hold a package for, use `calculate_mortgage_repayment` instead. Pass a `packageId` from `recommend_packages`, plus `loanAmount` and `tenureYears`. Always returns the full canonical schedule: loan facts (`context`), the per-tier breakdown (`periods`), rate structure, totals, and the per-year schedule (`annual`). The month-by-month schedule (`monthly`) is empty unless you pass `includeMonthly: true`. To also generate downloadable files, pass `formats` (`["pdf"]`, `["xlsx"]`, or both) — the public URLs come back under `documents` (null otherwise). Optional `firstPaymentDate` (YYYY-MM-DD, defaults to next month) dates the schedule.

calculate_package_schedule input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "packageId",
    "loanAmount",
    "tenureYears"
  ],
  "properties": {
    "formats": {
      "type": "array",
      "items": {
        "enum": [
          "pdf",
          "xlsx"
        ],
        "type": "string"
      },
      "description": "Document formats to generate and upload (pdf, xlsx); the resulting download URLs are returned under `documents`. Omit/empty for data only."
    },
    "packageId": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
      "description": "Package id, as returned by `recommend_packages`."
    },
    "loanAmount": {
      "type": "integer",
      "maximum": 9007199254740991,
      "minimum": -9007199254740991,
      "description": "Amount borrowed under this package, in SGD."
    },
    "tenureYears": {
      "type": "integer",
      "maximum": 35,
      "minimum": 1,
      "description": "Repayment period of the loan, in years; the schedule amortises over it."
    },
    "includeMonthly": {
      "type": "boolean",
      "description": "Include the full month-by-month schedule (`monthly[]`) in the response. Omitted/false returns an empty `monthly[]` to keep the payload small."
    },
    "firstPaymentDate": {
      "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": "First repayment month as an ISO date (YYYY-MM-DD). Defaults to next month."
    },
    "maskBankIdentity": {
      "type": "boolean",
      "description": "When true and the bank has maskIdentity set, replace the bank name with \"Confidential Rate\" and hide the logo in generated documents."
    }
  }
}
calculate_package_schedule output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "context",
    "periods",
    "rateStructure",
    "referenceIndex",
    "monthlyPayments",
    "totals",
    "annual",
    "monthly",
    "documents"
  ],
  "properties": {
    "annual": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "year",
          "interestPaid",
          "principalPaid",
          "totalPaid",
          "endingBalance"
        ],
        "properties": {
          "year": {
            "type": "integer",
            "maximum": 9007199254740991,
            "minimum": -9007199254740991,
            "description": "1-based loan year."
          },
          "totalPaid": {
            "type": "number",
            "description": "Total paid during the year (principal + interest), in SGD."
          },
          "interestPaid": {
            "type": "number",
            "description": "Interest paid during the year, in SGD."
          },
          "endingBalance": {
            "type": "number",
            "description": "Outstanding balance at year end, in SGD."
          },
          "principalPaid": {
            "type": "number",
            "description": "Principal repaid during the year, in SGD."
          }
        },
        "additionalProperties": false
      },
      "description": "Per-year rollup across the full tenure."
    },
    "totals": {
      "type": "object",
      "required": [
        "totalInterest",
        "totalPrincipal",
        "totalPaid",
        "interestPctOfLoan"
      ],
      "properties": {
        "totalPaid": {
          "type": "number",
          "description": "Total of all payments over the tenure, in SGD."
        },
        "totalInterest": {
          "type": "number",
          "description": "Total interest over the full tenure, in SGD."
        },
        "totalPrincipal": {
          "type": "number",
          "description": "Total principal repaid (equals the loan amount), in SGD."
        },
        "interestPctOfLoan": {
          "type": "number",
          "description": "Total interest as a fraction of the loan (0.42 = 42% of principal)."
        }
      },
      "description": "Totals over the full tenure.",
      "additionalProperties": false
    },
    "context": {
      "type": "object",
      "required": [
        "packageId",
        "bankName",
        "bankLogoUrl",
        "loanAmount",
        "tenureYears",
        "tenureMonths",
        "propertyLabel",
        "lockInYears",
        "firstPaymentLabel",
        "preparedLabel"
      ],
      "properties": {
        "bankName": {
          "type": "string",
          "description": "Lender name."
        },
        "packageId": {
          "type": "string",
          "format": "uuid",
          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
          "description": "The package this schedule was computed for."
        },
        "loanAmount": {
          "type": "number",
          "description": "Loan principal, in SGD."
        },
        "bankLogoUrl": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "description": "Lender logo URL, or null."
        },
        "lockInYears": {
          "anyOf": [
            {
              "type": "integer",
              "maximum": 9007199254740991,
              "minimum": -9007199254740991
            },
            {
              "type": "null"
            }
          ],
          "description": "Lock-in period in years, or null."
        },
        "tenureYears": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "Loan tenure in years."
        },
        "tenureMonths": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "Loan tenure in months (tenureYears × 12)."
        },
        "preparedLabel": {
          "type": "string",
          "description": "Report generation date, e.g. \"4 Jun 2026\"."
        },
        "propertyLabel": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "description": "Property type/status label, e.g. \"Private (completed)\", or null."
        },
        "firstPaymentLabel": {
          "type": "string",
          "description": "First repayment month, e.g. \"Aug 2026\"."
        }
      },
      "description": "Loan facts and presentation labels.",
      "additionalProperties": false
    },
    "monthly": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "monthIndex",
          "yearIndex",
          "monthLabel",
          "rate",
          "rateLabel",
          "openingBalance",
          "payment",
          "interest",
          "principal",
          "closingBalance",
          "cumulativeInterest",
          "cumulativePrincipal"
        ],
        "properties": {
          "rate": {
            "type": "number",
            "description": "Annual rate in effect, as a decimal fraction (0.035 = 3.5% p.a.)."
          },
          "payment": {
            "type": "number",
            "description": "Total payment this month, in SGD."
          },
          "interest": {
            "type": "number",
            "description": "Interest portion of the payment, in SGD."
          },
          "principal": {
            "type": "number",
            "description": "Principal portion of the payment, in SGD."
          },
          "rateLabel": {
            "type": "string",
            "description": "Rate description in effect this month."
          },
          "yearIndex": {
            "type": "integer",
            "maximum": 9007199254740991,
            "minimum": -9007199254740991,
            "description": "1-based loan year this month falls in."
          },
          "monthIndex": {
            "type": "integer",
            "maximum": 9007199254740991,
            "minimum": -9007199254740991,
            "description": "1-based payment number (1 … total months)."
          },
          "monthLabel": {
            "type": "string",
            "description": "Payment month, e.g. \"Aug 2026\"."
          },
          "closingBalance": {
            "type": "number",
            "description": "Balance at the end of the month, in SGD."
          },
          "openingBalance": {
            "type": "number",
            "description": "Balance at the start of the month, in SGD."
          },
          "cumulativeInterest": {
            "type": "number",
            "description": "Interest paid to date, in SGD."
          },
          "cumulativePrincipal": {
            "type": "number",
            "description": "Principal repaid to date, in SGD."
          }
        },
        "additionalProperties": false
      },
      "description": "Full month-by-month schedule; empty unless `includeMonthly`/`formats` requested."
    },
    "periods": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "year",
          "rate",
          "rateLabel",
          "monthlyPayment",
          "principalPaid",
          "interestPaid",
          "endingBalance",
          "months"
        ],
        "properties": {
          "rate": {
            "type": "number",
            "description": "Annual interest rate for the tier, as a decimal fraction (0.035 = 3.5% p.a.)."
          },
          "year": {
            "type": "integer",
            "maximum": 9007199254740991,
            "minimum": -9007199254740991,
            "description": "1-based loan year this rate tier begins in."
          },
          "months": {
            "type": "integer",
            "maximum": 9007199254740991,
            "minimum": -9007199254740991,
            "description": "Number of months this tier spans."
          },
          "rateLabel": {
            "type": "string",
            "description": "Human-readable rate label, e.g. \"Fixed\" or \"3M SORA + 1.00%\"."
          },
          "interestPaid": {
            "type": "number",
            "description": "Total interest paid during this tier, in SGD."
          },
          "endingBalance": {
            "type": "number",
            "description": "Outstanding balance at the end of this tier, in SGD."
          },
          "principalPaid": {
            "type": "number",
            "description": "Total principal repaid during this tier, in SGD."
          },
          "monthlyPayment": {
            "type": "number",
            "description": "Monthly instalment during this tier, in SGD."
          }
        },
        "additionalProperties": false
      },
      "description": "Compact per-rate-tier breakdown (monthly payment, principal/interest, balance)."
    },
    "documents": {
      "type": "object",
      "required": [
        "pdfUrl",
        "xlsxUrl"
      ],
      "properties": {
        "pdfUrl": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "description": "Public PDF download URL, or null if not generated."
        },
        "xlsxUrl": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "description": "Public XLSX download URL, or null if not generated."
        }
      },
      "description": "Generated PDF/XLSX download URLs.",
      "additionalProperties": false
    },
    "rateStructure": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "label",
          "rateLabel",
          "rate"
        ],
        "properties": {
          "rate": {
            "type": "number",
            "description": "Annual rate for the tier, as a decimal fraction (0.035 = 3.5% p.a.)."
          },
          "label": {
            "type": "string",
            "description": "Tier label, e.g. \"Year 1–2 (fixed)\" or \"Year 4 onward · 3M SORA\"."
          },
          "rateLabel": {
            "type": "string",
            "description": "Rate description, e.g. \"Fixed\" or \"3M SORA + 1.00%\"."
          }
        },
        "additionalProperties": false
      },
      "description": "Rate per tier, for display."
    },
    "referenceIndex": {
      "anyOf": [
        {
          "type": "object",
          "required": [
            "name",
            "rate",
            "asOfDate"
          ],
          "properties": {
            "name": {
              "type": "string",
              "description": "Benchmark name, e.g. \"3M SORA\"."
            },
            "rate": {
              "type": "number",
              "description": "Latest benchmark rate, as a decimal fraction (0.0356 = 3.56% p.a.)."
            },
            "asOfDate": {
              "type": "string",
              "description": "Date of the benchmark quote (ISO date, YYYY-MM-DD)."
            }
          },
          "additionalProperties": false
        },
        {
          "type": "null"
        }
      ],
      "description": "Benchmark the first floating tier is priced off, or null for fully fixed packages."
    },
    "monthlyPayments": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "label",
          "monthlyPayment"
        ],
        "properties": {
          "label": {
            "type": "string",
            "description": "Tier label, e.g. \"Year 1–2\"."
          },
          "monthlyPayment": {
            "type": "number",
            "description": "Monthly instalment during this tier, in SGD."
          }
        },
        "additionalProperties": false
      },
      "description": "Monthly instalment per tier."
    }
  },
  "additionalProperties": false
}
calculate_package_schedule annotations
{
  "title": "Calculate package payment schedule",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
list_market_indices — List market indices
Advertised by the server during the last successful anonymous check.
Advertised read-only

List market benchmark indices (id, slug, name, currency), sorted by slug. Use this to resolve a benchmark name like 'SORA' into the `marketIndexId` required by `get_index_quotes`.

list_market_indices input schema
{
  "type": "object",
  "properties": {}
}
list_market_indices output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "indices"
  ],
  "properties": {
    "indices": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "id",
          "slug",
          "name",
          "currency"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Index id; pass it as `marketIndexId` to `get_index_quotes`."
          },
          "name": {
            "type": "string",
            "description": "Display name of the benchmark."
          },
          "slug": {
            "type": "string",
            "description": "Short stable identifier of the benchmark."
          },
          "currency": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Currency the benchmark is quoted in, or null when not recorded."
          }
        },
        "additionalProperties": false
      },
      "description": "Benchmark indices, sorted by slug."
    }
  },
  "additionalProperties": false
}
list_market_indices annotations
{
  "title": "List market indices",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
get_index_quotes — Get market index quotes
Advertised by the server during the last successful anonymous check.
Advertised read-only

Return historical quotes (rate by date) for a market benchmark index such as SORA. Requires `marketIndexId` — use `list_market_indices` to resolve a benchmark name/slug into its UUID. Optional `startDate`/`endDate` narrow the range; results are paginated, newest first. Quote `rate` values are decimal fractions (0.0356 = 3.56% p.a.).

get_index_quotes input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "marketIndexId"
  ],
  "properties": {
    "page": {
      "type": "integer",
      "default": 1,
      "maximum": 9007199254740991,
      "minimum": 1,
      "description": "Page of results to return, counting from 1; `totalPages` gives the last."
    },
    "limit": {
      "type": "integer",
      "default": 50,
      "maximum": 100,
      "minimum": 1,
      "description": "Number of quotes per page."
    },
    "endDate": {
      "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": "Latest quote date to include. Omit for no upper bound."
    },
    "startDate": {
      "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": "Earliest quote date to include. Omit for no lower bound."
    },
    "marketIndexId": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
      "description": "Market index UUID. Resolve a benchmark name/slug via `list_market_indices`."
    }
  }
}
get_index_quotes output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "items",
    "total",
    "page",
    "pageSize",
    "totalPages"
  ],
  "properties": {
    "page": {
      "type": "integer",
      "maximum": 9007199254740991,
      "minimum": -9007199254740991,
      "description": "1-based page number returned."
    },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "id",
          "marketIndexId",
          "asOfDate",
          "rate"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Quote id."
          },
          "rate": {
            "type": "number",
            "description": "Benchmark rate on that date, as a decimal fraction (0.0356 = 3.56% p.a.)."
          },
          "asOfDate": {
            "type": "string",
            "description": "Quote date (ISO date, YYYY-MM-DD)."
          },
          "marketIndexId": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Owning market index id."
          }
        },
        "additionalProperties": false
      },
      "description": "Quotes for the page, newest first."
    },
    "total": {
      "type": "integer",
      "maximum": 9007199254740991,
      "minimum": -9007199254740991,
      "description": "Total matching quotes across all pages."
    },
    "pageSize": {
      "type": "integer",
      "maximum": 9007199254740991,
      "minimum": -9007199254740991,
      "description": "Page size used."
    },
    "totalPages": {
      "type": "integer",
      "maximum": 9007199254740991,
      "minimum": -9007199254740991,
      "description": "Total number of pages."
    }
  },
  "additionalProperties": false
}
get_index_quotes annotations
{
  "title": "Get market index quotes",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
calculate_affordability — Affordability calculator
Advertised by the server during the last successful anonymous check.
Advertised read-only

Estimate the maximum loan and property price a borrower can afford from income, existing debt commitments and tenure, applying Singapore TDSR/MSR limits. Use this when you know the income and want the budget; to go the other way (a target price → the income needed), use `calculate_required_income`.

calculate_affordability input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "monthlyFixedIncome"
  ],
  "properties": {
    "age": {
      "type": "integer",
      "default": 35,
      "maximum": 80,
      "minimum": 21,
      "description": "Age of the (oldest) borrower."
    },
    "cpfOA": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "CPF Ordinary Account balance usable, in SGD."
    },
    "cashSavings": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Cash available for downpayment, in SGD."
    },
    "citizenship": {
      "enum": [
        "singaporean",
        "pr",
        "foreigner"
      ],
      "type": "string",
      "default": "singaporean",
      "description": "Buyer citizenship: singaporean, pr or foreigner. Drives ABSD rate."
    },
    "monthlyDebt": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Total existing monthly debt commitments (other loans, cards), in SGD."
    },
    "interestRate": {
      "type": "number",
      "default": 4,
      "maximum": 15,
      "minimum": 0.1,
      "description": "Assumed annual interest rate, as a percentage (e.g. 4.0 = 4%, not 0.04)."
    },
    "propertyType": {
      "enum": [
        "hdb",
        "ec",
        "private"
      ],
      "type": "string",
      "default": "hdb",
      "description": "Property type: hdb, ec (Executive Condominium) or private. Sets MSR/LTV rules."
    },
    "stressTestRate": {
      "type": "number",
      "maximum": 15,
      "minimum": 0.1,
      "description": "Override the TDSR/MSR stress-test rate (percentage); defaults to the MAS floor."
    },
    "loanTenureYears": {
      "type": "integer",
      "default": 25,
      "maximum": 35,
      "minimum": 1,
      "description": "Repayment period, in years, over which the maximum loan is sized."
    },
    "existingProperty": {
      "type": "boolean",
      "default": false,
      "description": "True if the borrower already owns ≥1 property (affects ABSD and LTV)."
    },
    "monthlyFixedIncome": {
      "type": "number",
      "minimum": 0,
      "description": "Gross fixed monthly income (salary), in SGD."
    },
    "monthlyVariableIncome": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Gross variable monthly income (bonus/commission), in SGD; haircut 30% under TDSR."
    },
    "existingPropertiesOwned": {
      "type": "integer",
      "maximum": 10,
      "minimum": 0,
      "description": "Count of residential properties already owned; sets the ABSD tier (0, 1 or 2+). Takes precedence over existingProperty when provided."
    }
  }
}
calculate_affordability output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "maxPropertyPrice",
    "maxLoan",
    "monthlyInstalment",
    "msrLimit",
    "tdsrLimit",
    "limitingFactor",
    "minCashDownpayment",
    "cpfUsageAllowed",
    "downpaymentTotal",
    "bsd",
    "absd",
    "absdRate",
    "ltv",
    "minCashPercent",
    "totalCashRequired"
  ],
  "properties": {
    "bsd": {
      "type": "number",
      "description": "Buyer's Stamp Duty, in SGD."
    },
    "ltv": {
      "type": "number",
      "description": "Loan-to-value ratio applied, as a decimal fraction (0.75 = 75%)."
    },
    "absd": {
      "type": "number",
      "description": "Additional Buyer’s Stamp Duty, in SGD."
    },
    "maxLoan": {
      "type": "number",
      "description": "Maximum loan the borrower qualifies for, in SGD."
    },
    "absdRate": {
      "type": "number",
      "description": "ABSD rate applied, as a decimal fraction (0.2 = 20%)."
    },
    "msrLimit": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "description": "Monthly MSR cap (HDB/EC only), in SGD; null otherwise."
    },
    "tdsrLimit": {
      "type": "number",
      "description": "Monthly TDSR cap, in SGD."
    },
    "limitingFactor": {
      "anyOf": [
        {
          "enum": [
            "msr",
            "tdsr"
          ],
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "description": "Which limit bound the loan: \"msr\" or \"tdsr\" (null if neither binds)."
    },
    "minCashPercent": {
      "type": "number",
      "description": "Minimum cash portion of price, as a decimal fraction."
    },
    "cpfUsageAllowed": {
      "type": "number",
      "description": "CPF allowed toward downpayment, in SGD."
    },
    "downpaymentTotal": {
      "type": "number",
      "description": "Total downpayment (cash + CPF), in SGD."
    },
    "maxPropertyPrice": {
      "type": "number",
      "description": "Maximum affordable property price, in SGD."
    },
    "monthlyInstalment": {
      "type": "number",
      "description": "Estimated monthly instalment at that loan, in SGD."
    },
    "totalCashRequired": {
      "type": "number",
      "description": "Total upfront cash needed (downpayment + duties + fees), in SGD."
    },
    "minCashDownpayment": {
      "type": "number",
      "description": "Minimum cash downpayment required, in SGD."
    }
  },
  "additionalProperties": false
}
calculate_affordability annotations
{
  "title": "Affordability calculator",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
calculate_required_income — Required-income calculator
Advertised by the server during the last successful anonymous check.
Advertised read-only

Reverse affordability: work out the gross monthly income needed to support a target property price under Singapore TDSR/MSR limits. Use this when you know the target property price; to go the other way (income → maximum budget), use `calculate_affordability`.

calculate_required_income input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "propertyPrice"
  ],
  "properties": {
    "age": {
      "type": "integer",
      "default": 35,
      "maximum": 80,
      "minimum": 21,
      "description": "Age of the (oldest) borrower."
    },
    "cpfOA": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "CPF Ordinary Account balance usable, in SGD."
    },
    "cashSavings": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Cash available for downpayment, in SGD."
    },
    "citizenship": {
      "enum": [
        "singaporean",
        "pr",
        "foreigner"
      ],
      "type": "string",
      "default": "singaporean",
      "description": "Buyer's residency status (Singapore citizen, permanent resident or foreigner); sets the ABSD rate."
    },
    "monthlyDebt": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Total existing monthly debt commitments, in SGD."
    },
    "interestRate": {
      "type": "number",
      "default": 4,
      "maximum": 15,
      "minimum": 0.1,
      "description": "Assumed annual interest rate, as a percentage (e.g. 4.0 = 4%, not 0.04)."
    },
    "propertyType": {
      "enum": [
        "hdb",
        "ec",
        "private"
      ],
      "type": "string",
      "default": "hdb",
      "description": "Type of property: an HDB flat, an Executive Condominium (ec) or private. HDB and EC purchases are also held to the MSR limit."
    },
    "propertyPrice": {
      "type": "number",
      "minimum": 0,
      "description": "Target property purchase price, in SGD."
    },
    "stressTestRate": {
      "type": "number",
      "maximum": 15,
      "minimum": 0.1,
      "description": "Override the TDSR/MSR stress-test rate (percentage); defaults to the MAS floor."
    },
    "loanTenureYears": {
      "type": "integer",
      "default": 25,
      "maximum": 35,
      "minimum": 1,
      "description": "Loan tenure in years."
    },
    "existingProperty": {
      "type": "boolean",
      "default": false,
      "description": "True if the borrower already owns ≥1 property (affects ABSD and LTV)."
    },
    "existingPropertiesOwned": {
      "type": "integer",
      "maximum": 10,
      "minimum": 0,
      "description": "Count of residential properties already owned; sets the ABSD tier (0, 1 or 2+). Takes precedence over existingProperty when provided."
    }
  }
}
calculate_required_income output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "maxLoan",
    "requiredIncome",
    "monthlyPayment",
    "msrRequired",
    "tdsrRequired",
    "minCashDownpayment",
    "cpfDownpayment",
    "downpaymentTotal",
    "bsd",
    "absd",
    "absdRate",
    "cashShortfall",
    "cpfShortfall",
    "ltv",
    "minCashPercent",
    "isAffordable"
  ],
  "properties": {
    "bsd": {
      "type": "number",
      "description": "Buyer's Stamp Duty, in SGD."
    },
    "ltv": {
      "type": "number",
      "description": "Loan-to-value ratio, as a decimal fraction (0.75 = 75%)."
    },
    "absd": {
      "type": "number",
      "description": "Additional Buyer’s Stamp Duty, in SGD."
    },
    "maxLoan": {
      "type": "number",
      "description": "Loan needed to buy at the target price, in SGD."
    },
    "absdRate": {
      "type": "number",
      "description": "ABSD rate applied, as a decimal fraction (0.2 = 20%)."
    },
    "msrRequired": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "description": "Monthly MSR needed (HDB/EC), in SGD; null otherwise."
    },
    "cpfShortfall": {
      "type": "number",
      "description": "CPF shortfall vs available OA, in SGD (0 if none)."
    },
    "isAffordable": {
      "type": "boolean",
      "description": "True if the borrower can fund the purchase with stated means."
    },
    "tdsrRequired": {
      "type": "number",
      "description": "Monthly TDSR needed, in SGD."
    },
    "cashShortfall": {
      "type": "number",
      "description": "Cash shortfall vs available savings, in SGD (0 if none)."
    },
    "cpfDownpayment": {
      "type": "number",
      "description": "CPF applied to downpayment, in SGD."
    },
    "minCashPercent": {
      "type": "number",
      "description": "Minimum cash portion of price, as a decimal fraction."
    },
    "monthlyPayment": {
      "type": "number",
      "description": "Monthly instalment at that loan, in SGD."
    },
    "requiredIncome": {
      "type": "number",
      "description": "Gross monthly income required to support it, in SGD."
    },
    "downpaymentTotal": {
      "type": "number",
      "description": "Total downpayment (cash + CPF), in SGD."
    },
    "minCashDownpayment": {
      "type": "number",
      "description": "Minimum cash downpayment, in SGD."
    }
  },
  "additionalProperties": false
}
calculate_required_income annotations
{
  "title": "Required-income calculator",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
calculate_mortgage_repayment — Mortgage repayment calculator
Advertised by the server during the last successful anonymous check.
Advertised read-only

Compute the monthly repayment, total interest and total cost for a hypothetical loan from an interest rate you supply (standard amortisation). Use this for "what-if" rates; for the real rates of a specific package the borrower has chosen, use `calculate_package_schedule` instead.

calculate_mortgage_repayment input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "loanAmount",
    "interestRate",
    "tenureYears"
  ],
  "properties": {
    "loanAmount": {
      "type": "number",
      "maximum": 200000000,
      "minimum": 50000,
      "description": "Amount borrowed, in SGD."
    },
    "tenureYears": {
      "type": "integer",
      "maximum": 35,
      "minimum": 1,
      "description": "Loan tenure in years."
    },
    "interestRate": {
      "type": "number",
      "maximum": 15,
      "minimum": 0.1,
      "description": "Hypothetical annual interest rate held for the whole tenure, as a percentage (e.g. 3.5 = 3.5%, not 0.035)."
    }
  }
}
calculate_mortgage_repayment output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "monthlyPayment",
    "totalInterest",
    "totalPaid",
    "firstYearPrincipal",
    "firstYearInterest",
    "halfPaidOffMonth",
    "interestPercentage",
    "principalPercentage"
  ],
  "properties": {
    "totalPaid": {
      "type": "number",
      "description": "Total of all payments (principal + interest), in SGD."
    },
    "totalInterest": {
      "type": "number",
      "description": "Total interest over the tenure, in SGD."
    },
    "monthlyPayment": {
      "type": "number",
      "description": "Monthly instalment, in SGD."
    },
    "halfPaidOffMonth": {
      "type": "integer",
      "maximum": 9007199254740991,
      "minimum": -9007199254740991,
      "description": "Month at which half the principal is repaid."
    },
    "firstYearInterest": {
      "type": "number",
      "description": "Interest paid in year 1, in SGD."
    },
    "firstYearPrincipal": {
      "type": "number",
      "description": "Principal repaid in year 1, in SGD."
    },
    "interestPercentage": {
      "type": "number",
      "description": "Interest as a fraction of total paid (0.3 = 30%)."
    },
    "principalPercentage": {
      "type": "number",
      "description": "Principal as a fraction of total paid (0.7 = 70%)."
    }
  },
  "additionalProperties": false
}
calculate_mortgage_repayment annotations
{
  "title": "Mortgage repayment calculator",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
calculate_refinance_savings — Refinance savings calculator
Advertised by the server during the last successful anonymous check.
Advertised read-only

Compare a borrower's existing mortgage against a new rate and report monthly savings, total savings and break-even months. Set `mode` to 'original' (supply the original loan amount, tenure and start date) or 'remaining' (supply the current outstanding balance and remaining tenure).

calculate_refinance_savings input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "mode",
    "currentRate",
    "newRate"
  ],
  "properties": {
    "mode": {
      "enum": [
        "original",
        "remaining"
      ],
      "type": "string",
      "description": "Use 'original' when supplying the loan's original terms and start date, 'remaining' when supplying the current outstanding balance and tenure left."
    },
    "newRate": {
      "type": "number",
      "maximum": 15,
      "minimum": 0.1,
      "description": "Annual rate offered on the new loan, as a percentage (e.g. 3.2 = 3.2%)."
    },
    "currentRate": {
      "type": "number",
      "maximum": 15,
      "minimum": 0.1,
      "description": "Annual rate on the existing loan, as a percentage (e.g. 4.0 = 4%)."
    },
    "loanStartYear": {
      "type": "integer",
      "maximum": 2100,
      "minimum": 1990,
      "description": "Mode 'original': calendar year the loan started, e.g. 2021."
    },
    "loanStartMonth": {
      "type": "integer",
      "maximum": 12,
      "minimum": 1,
      "description": "Mode 'original': month the loan started, as a number (1 = January)."
    },
    "newTenureYears": {
      "type": "integer",
      "maximum": 35,
      "minimum": 1,
      "description": "Tenure of the new loan, in years. Omit to keep the current loan’s remaining tenure."
    },
    "remainingAmount": {
      "type": "number",
      "minimum": 0,
      "description": "Mode 'remaining': the balance still owed today, in SGD."
    },
    "refinancingCosts": {
      "type": "number",
      "default": 2500,
      "minimum": 0,
      "description": "One-off cost of switching (legal and valuation fees), in SGD; deducted from the savings and recouped in the break-even."
    },
    "originalLoanAmount": {
      "type": "number",
      "minimum": 0,
      "description": "Mode 'original': the amount first borrowed, in SGD. Today's balance is worked out from it."
    },
    "originalTenureYears": {
      "type": "integer",
      "maximum": 35,
      "minimum": 1,
      "description": "Mode 'original': the tenure the loan was first taken over, in years."
    },
    "remainingTenureYears": {
      "type": "integer",
      "maximum": 35,
      "minimum": 1,
      "description": "Mode 'remaining': years left to repay on the current loan."
    },
    "earlyRedemptionPenalty": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Penalty the current bank charges for repaying within its lock-in, in SGD; treated as a switching cost like refinancingCosts."
    }
  }
}
calculate_refinance_savings output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "remainingBalance",
    "remainingTenureMonths",
    "currentMonthly",
    "newMonthly",
    "monthlySavings",
    "totalCurrentCost",
    "totalNewCost",
    "totalSavings",
    "breakEvenMonths",
    "isBeneficial"
  ],
  "properties": {
    "warning": {
      "type": "string",
      "description": "Set when the loan start/tenure was clamped (e.g. loan already fully repaid)."
    },
    "newMonthly": {
      "type": "number",
      "description": "Monthly instalment after refinancing, in SGD."
    },
    "isBeneficial": {
      "type": "boolean",
      "description": "True if refinancing saves money overall."
    },
    "totalNewCost": {
      "type": "number",
      "description": "Total cost on the new loan incl. switching costs, in SGD."
    },
    "totalSavings": {
      "type": "number",
      "description": "Net saving over the remaining tenure, in SGD."
    },
    "currentMonthly": {
      "type": "number",
      "description": "Current monthly instalment, in SGD."
    },
    "monthlySavings": {
      "type": "number",
      "description": "Monthly saving (current − new), in SGD."
    },
    "breakEvenMonths": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "description": "Months to recoup switching costs, or null if it never breaks even."
    },
    "remainingBalance": {
      "type": "number",
      "description": "Outstanding balance being refinanced, in SGD."
    },
    "totalCurrentCost": {
      "type": "number",
      "description": "Total remaining cost on the current loan, in SGD."
    },
    "remainingTenureMonths": {
      "type": "integer",
      "maximum": 9007199254740991,
      "minimum": -9007199254740991,
      "description": "Remaining tenure, in months."
    }
  },
  "additionalProperties": false
}
calculate_refinance_savings annotations
{
  "title": "Refinance savings calculator",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
calculate_residential_stamp_duty — Residential stamp duty (BSD + ABSD)
Advertised by the server during the last successful anonymous check.
Advertised read-only

Compute Buyer's Stamp Duty (BSD) and Additional Buyer's Stamp Duty (ABSD) for a Singapore residential purchase, given price/value, buyer type (singaporean | pr | foreigner | entity | trust) and the number of residential properties already owned.

calculate_residential_stamp_duty input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "price"
  ],
  "properties": {
    "price": {
      "type": "number",
      "minimum": 0,
      "description": "Purchase price or market value (whichever higher), in SGD."
    },
    "buyerType": {
      "enum": [
        "singaporean",
        "pr",
        "foreigner",
        "entity",
        "trust"
      ],
      "type": "string",
      "default": "singaporean",
      "description": "Residency or legal status of the buyer (citizen, permanent resident, foreigner, company or trust); sets the ABSD rate."
    },
    "propertyType": {
      "enum": [
        "hdb",
        "ec",
        "private"
      ],
      "type": "string",
      "default": "hdb",
      "description": "Type of residential property being bought. It does not change the result: BSD and ABSD depend on price, buyer and properties already owned."
    },
    "purchaseType": {
      "enum": [
        "single",
        "joint_spouse",
        "joint_non_spouse",
        "entity",
        "trust"
      ],
      "type": "string",
      "default": "single",
      "description": "How the property will be held. `entity` (a company) or `trust` applies that ABSD rate whatever `buyerType` says; for the individual options the rate follows `buyerType`."
    },
    "existingPropertiesOwned": {
      "type": "integer",
      "default": 0,
      "maximum": 10,
      "minimum": 0,
      "description": "Count of residential properties the buyer already owns (sets the ABSD tier)."
    }
  }
}
calculate_residential_stamp_duty output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "bsd",
    "bsdBreakdown",
    "absdRate",
    "absd",
    "totalBuyerStampDuty"
  ],
  "properties": {
    "bsd": {
      "type": "number",
      "description": "Buyer's Stamp Duty, in SGD."
    },
    "absd": {
      "type": "number",
      "description": "Additional Buyer’s Stamp Duty, in SGD."
    },
    "absdRate": {
      "type": "number",
      "description": "ABSD rate applied, as a decimal fraction (0.2 = 20%)."
    },
    "bsdBreakdown": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "bracket",
          "amount"
        ],
        "properties": {
          "amount": {
            "type": "number"
          },
          "bracket": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "description": "BSD amount per price bracket."
    },
    "totalBuyerStampDuty": {
      "type": "number",
      "description": "Total stamp duty (BSD + ABSD), in SGD."
    }
  },
  "additionalProperties": false
}
calculate_residential_stamp_duty annotations
{
  "title": "Residential stamp duty (BSD + ABSD)",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
calculate_rental_stamp_duty — Rental stamp duty
Advertised by the server during the last successful anonymous check.
Advertised read-only

Compute the stamp duty payable on a property lease from the rent and lease term.

calculate_rental_stamp_duty input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "monthlyRent"
  ],
  "properties": {
    "hasPremium": {
      "type": "boolean",
      "default": false,
      "description": "True if the lease carries a lump-sum premium."
    },
    "monthlyRent": {
      "type": "number",
      "minimum": 0,
      "description": "Monthly rent under the lease, in SGD."
    },
    "premiumAmount": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "One-off premium paid on top of the rent, in SGD; only counted when `hasPremium` is true."
    },
    "leasePeriodMonths": {
      "type": "integer",
      "default": 12,
      "maximum": 1200,
      "minimum": 1,
      "description": "Lease term in months."
    }
  }
}
calculate_rental_stamp_duty output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "totalRental",
    "stampDuty"
  ],
  "properties": {
    "stampDuty": {
      "type": "number",
      "description": "Lease stamp duty payable, in SGD."
    },
    "totalRental": {
      "type": "number",
      "description": "Total rent over the lease (incl. premium), in SGD."
    }
  },
  "additionalProperties": false
}
calculate_rental_stamp_duty annotations
{
  "title": "Rental stamp duty",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
calculate_mortgage_stamp_duty — Mortgage stamp duty
Advertised by the server during the last successful anonymous check.
Advertised read-only

Compute the stamp duty payable on a mortgage/loan facility.

calculate_mortgage_stamp_duty input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "loanAmount"
  ],
  "properties": {
    "loanAmount": {
      "type": "number",
      "minimum": 0,
      "description": "Mortgage/loan facility amount, in SGD."
    }
  }
}
calculate_mortgage_stamp_duty output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "stampDuty"
  ],
  "properties": {
    "stampDuty": {
      "type": "number",
      "description": "Mortgage stamp duty payable, in SGD (capped at $500)."
    }
  },
  "additionalProperties": false
}
calculate_mortgage_stamp_duty annotations
{
  "title": "Mortgage stamp duty",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
calculate_seller_stamp_duty — Seller's Stamp Duty (SSD)
Advertised by the server during the last successful anonymous check.
Advertised read-only

Compute Seller's Stamp Duty (SSD) from the sale price/value and how long the property has been held.

calculate_seller_stamp_duty input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "price",
    "purchaseDate",
    "saleDate"
  ],
  "properties": {
    "price": {
      "type": "number",
      "minimum": 0,
      "description": "Sale price or market value (whichever higher), in SGD."
    },
    "saleDate": {
      "type": "string",
      "description": "Date the property is/was sold (ISO date, YYYY-MM-DD)."
    },
    "purchaseDate": {
      "type": "string",
      "description": "Date the property was acquired (ISO date, YYYY-MM-DD)."
    }
  }
}
calculate_seller_stamp_duty output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "ssdRate",
    "ssd",
    "holdingPeriodYears"
  ],
  "properties": {
    "ssd": {
      "type": "number",
      "description": "Seller's Stamp Duty payable, in SGD (0 past the holding period)."
    },
    "ssdRate": {
      "type": "number",
      "description": "Seller's Stamp Duty rate applied, as a decimal fraction (0.12 = 12%)."
    },
    "holdingPeriodYears": {
      "type": "number",
      "description": "Years the property was held (fractional)."
    }
  },
  "additionalProperties": false
}
calculate_seller_stamp_duty annotations
{
  "title": "Seller's Stamp Duty (SSD)",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
analyze_sell_one_buy_two — Sell-one-buy-two analysis
Advertised by the server during the last successful anonymous check.
Advertised read-only

Analyse a 'sell one, buy two' plan where two co-owners sell their joint home. Returns the sale proceeds (cash after loan, CPF refunds, SSD and legal fees) and each buyer's resulting cash and CPF. With `buy1`, it compares buying one replacement jointly or under either buyer alone; with `buy2Property1` and `buy2Property2`, each buyer buying one property (both pairings). Every option is given at 30- and 35-year tenures with max loan, LTV, instalment, downpayment, stamp duties, cash/CPF shortfalls, TDSR used and an affordability verdict.

analyze_sell_one_buy_two input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "valuation",
    "buyer1",
    "buyer2"
  ],
  "properties": {
    "buy1": {
      "type": "object",
      "required": [
        "propertyPrice"
      ],
      "properties": {
        "legalFees": {
          "type": "number",
          "default": 3000,
          "minimum": 0,
          "description": "Conveyancing fees for buying this property, in SGD; part of the cash required."
        },
        "propertyPrice": {
          "type": "number",
          "minimum": 0,
          "description": "Price of the property to buy, in SGD; loan, downpayment and stamp duties follow from it."
        },
        "loanInterestRate": {
          "type": "number",
          "default": 2.6,
          "maximum": 15,
          "minimum": 0.1,
          "description": "Annual rate assumed for this loan, as a percentage (e.g. 2.6 = 2.6%); sets the instalment. Affordability is tested at the higher of this and the MAS 4% floor."
        },
        "remainingLeaseYears": {
          "type": "number",
          "maximum": 99,
          "minimum": 0,
          "description": "Remaining lease in years; omit for freehold. When the lease will not last until the youngest buyer turns 95, the CPF usable on this property is prorated (none below 20 years)."
        }
      },
      "description": "The single property in the \"buy one\" scenario."
    },
    "buyer1": {
      "type": "object",
      "required": [
        "monthlyIncome"
      ],
      "properties": {
        "age": {
          "type": "integer",
          "default": 35,
          "maximum": 80,
          "minimum": 21,
          "description": "Buyer age in years. With income it sets the income-weighted average age, which picks the LTV tier (loan running past 65) and, on a short lease, the CPF usable."
        },
        "cpfOA": {
          "type": "number",
          "default": 0,
          "minimum": 0,
          "description": "CPF Ordinary Account balance before the sale, in SGD; added to the CPF refunded from the sale."
        },
        "miscIncome": {
          "type": "number",
          "default": 0,
          "minimum": 0,
          "description": "Other monthly income, in SGD; 70% of it counts towards TDSR."
        },
        "citizenship": {
          "enum": [
            "singaporean",
            "pr",
            "foreigner"
          ],
          "type": "string",
          "default": "singaporean",
          "description": "Buyer's residency status; sets the ABSD rate. On a joint purchase buyer 1's status is used."
        },
        "monthlyDebt": {
          "type": "number",
          "default": 0,
          "minimum": 0,
          "description": "Monthly repayments on other debts (car, personal loans, cards), in SGD; reduce TDSR headroom."
        },
        "monthlyIncome": {
          "type": "number",
          "minimum": 0,
          "description": "Gross monthly employment income, in SGD; counts towards TDSR."
        },
        "isVariableIncome": {
          "type": "boolean",
          "default": false,
          "description": "True when monthlyIncome is variable (commission or bonus), so only 70% of it counts towards TDSR."
        }
      },
      "description": "Income/CPF profile of buyer 1."
    },
    "buyer2": {
      "type": "object",
      "required": [
        "monthlyIncome"
      ],
      "properties": {
        "age": {
          "type": "integer",
          "default": 35,
          "maximum": 80,
          "minimum": 21,
          "description": "Buyer age in years. With income it sets the income-weighted average age, which picks the LTV tier (loan running past 65) and, on a short lease, the CPF usable."
        },
        "cpfOA": {
          "type": "number",
          "default": 0,
          "minimum": 0,
          "description": "CPF Ordinary Account balance before the sale, in SGD; added to the CPF refunded from the sale."
        },
        "miscIncome": {
          "type": "number",
          "default": 0,
          "minimum": 0,
          "description": "Other monthly income, in SGD; 70% of it counts towards TDSR."
        },
        "citizenship": {
          "enum": [
            "singaporean",
            "pr",
            "foreigner"
          ],
          "type": "string",
          "default": "singaporean",
          "description": "Buyer's residency status; sets the ABSD rate. On a joint purchase buyer 1's status is used."
        },
        "monthlyDebt": {
          "type": "number",
          "default": 0,
          "minimum": 0,
          "description": "Monthly repayments on other debts (car, personal loans, cards), in SGD; reduce TDSR headroom."
        },
        "monthlyIncome": {
          "type": "number",
          "minimum": 0,
          "description": "Gross monthly employment income, in SGD; counts towards TDSR."
        },
        "isVariableIncome": {
          "type": "boolean",
          "default": false,
          "description": "True when monthlyIncome is variable (commission or bonus), so only 70% of it counts towards TDSR."
        }
      },
      "description": "Income/CPF profile of buyer 2."
    },
    "valuation": {
      "type": "number",
      "minimum": 0,
      "description": "Expected sale price of the jointly owned property being sold, in SGD."
    },
    "buyer1Shares": {
      "type": "number",
      "default": 50,
      "maximum": 100,
      "minimum": 0,
      "description": "Buyer 1's ownership share of the sold property, in percent; splits the cash proceeds."
    },
    "buyer2Shares": {
      "type": "number",
      "maximum": 100,
      "minimum": 0,
      "description": "Buyer 2's ownership share of the sold property, in percent; splits the cash proceeds. Omit to use 100 minus buyer1Shares."
    },
    "buy2Property1": {
      "type": "object",
      "required": [
        "propertyPrice"
      ],
      "properties": {
        "legalFees": {
          "type": "number",
          "default": 3000,
          "minimum": 0,
          "description": "Conveyancing fees for buying this property, in SGD; part of the cash required."
        },
        "propertyPrice": {
          "type": "number",
          "minimum": 0,
          "description": "Price of the property to buy, in SGD; loan, downpayment and stamp duties follow from it."
        },
        "loanInterestRate": {
          "type": "number",
          "default": 2.6,
          "maximum": 15,
          "minimum": 0.1,
          "description": "Annual rate assumed for this loan, as a percentage (e.g. 2.6 = 2.6%); sets the instalment. Affordability is tested at the higher of this and the MAS 4% floor."
        },
        "remainingLeaseYears": {
          "type": "number",
          "maximum": 99,
          "minimum": 0,
          "description": "Remaining lease in years; omit for freehold. When the lease will not last until the youngest buyer turns 95, the CPF usable on this property is prorated (none below 20 years)."
        }
      },
      "description": "First property in the \"buy two\" scenario."
    },
    "buy2Property2": {
      "type": "object",
      "required": [
        "propertyPrice"
      ],
      "properties": {
        "legalFees": {
          "type": "number",
          "default": 3000,
          "minimum": 0,
          "description": "Conveyancing fees for buying this property, in SGD; part of the cash required."
        },
        "propertyPrice": {
          "type": "number",
          "minimum": 0,
          "description": "Price of the property to buy, in SGD; loan, downpayment and stamp duties follow from it."
        },
        "loanInterestRate": {
          "type": "number",
          "default": 2.6,
          "maximum": 15,
          "minimum": 0.1,
          "description": "Annual rate assumed for this loan, as a percentage (e.g. 2.6 = 2.6%); sets the instalment. Affordability is tested at the higher of this and the MAS 4% floor."
        },
        "remainingLeaseYears": {
          "type": "number",
          "maximum": 99,
          "minimum": 0,
          "description": "Remaining lease in years; omit for freehold. When the lease will not last until the youngest buyer turns 95, the CPF usable on this property is prorated (none below 20 years)."
        }
      },
      "description": "Second property in the \"buy two\" scenario."
    },
    "buyer1CpfUsage": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "CPF (with accrued interest) buyer 1 must refund on sale, in SGD."
    },
    "buyer2CpfUsage": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "CPF (with accrued interest) buyer 2 must refund on sale, in SGD."
    },
    "outstandingLoan": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Loan still owed on the property being sold, repaid from the sale, in SGD."
    },
    "sellerStampDuty": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Seller's Stamp Duty payable on the sale, in SGD."
    },
    "sellingLegalFees": {
      "type": "number",
      "default": 3000,
      "minimum": 0,
      "description": "Conveyancing fees for the sale, deducted from the proceeds, in SGD."
    }
  }
}
analyze_sell_one_buy_two output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "sale",
    "buyer1Funds",
    "buyer2Funds"
  ],
  "properties": {
    "sale": {
      "type": "object",
      "required": [
        "status",
        "valuation",
        "outstandingLoan",
        "buyer1CpfUsage",
        "buyer2CpfUsage",
        "sellerStampDuty",
        "legalFees",
        "cashProceeds"
      ],
      "properties": {
        "status": {
          "enum": [
            "positive",
            "negative"
          ],
          "type": "string",
          "description": "'positive' when the sale leaves cash; 'negative' when the sellers must top up."
        },
        "legalFees": {
          "type": "number",
          "description": "Conveyancing fees for the sale, in SGD."
        },
        "valuation": {
          "type": "number",
          "description": "Sale price used, in SGD."
        },
        "cashProceeds": {
          "type": "number",
          "description": "Cash left after the loan, both CPF refunds, SSD and legal fees, in SGD; negative means a top-up is needed."
        },
        "buyer1CpfUsage": {
          "type": "number",
          "description": "CPF refunded to buyer 1's account, in SGD."
        },
        "buyer2CpfUsage": {
          "type": "number",
          "description": "CPF refunded to buyer 2's account, in SGD."
        },
        "outstandingLoan": {
          "type": "number",
          "description": "Loan repaid from the sale, in SGD."
        },
        "sellerStampDuty": {
          "type": "number",
          "description": "Seller's Stamp Duty paid, in SGD."
        }
      },
      "description": "Proceeds from selling the current property.",
      "additionalProperties": false
    },
    "buy1Options": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "label",
          "scenario30yr",
          "scenario35yr"
        ],
        "properties": {
          "label": {
            "type": "string",
            "description": "Who buys, and which property."
          },
          "scenario30yr": {
            "type": "object",
            "required": [
              "label",
              "tenureYears",
              "maxLoan",
              "ltv",
              "monthlyInstalment",
              "downpaymentTotal",
              "minCashDownpayment",
              "cpfForDownpayment",
              "maxCpfUsage",
              "bsd",
              "absdRate",
              "absd",
              "legalFees",
              "totalCashRequired",
              "cashShortfall",
              "cpfShortfall",
              "isAffordable",
              "tdsrUsed",
              "msrUsed"
            ],
            "properties": {
              "bsd": {
                "type": "number",
                "description": "Buyer's Stamp Duty, in SGD."
              },
              "ltv": {
                "type": "number",
                "description": "Loan-to-value applied, as a decimal fraction (0.75 = 75%)."
              },
              "absd": {
                "type": "number",
                "description": "Additional Buyer’s Stamp Duty, in SGD."
              },
              "label": {
                "type": "string",
                "description": "Who buys which property in this scenario."
              },
              "maxLoan": {
                "type": "number",
                "description": "Largest loan at the LTV for this tenure and age, in SGD; 0 when the loan would run past age 75."
              },
              "msrUsed": {
                "anyOf": [
                  {
                    "type": "number"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Always null: MSR is not assessed here."
              },
              "absdRate": {
                "type": "number",
                "description": "ABSD rate applied, as a decimal fraction (0.2 = 20%)."
              },
              "tdsrUsed": {
                "type": "number",
                "description": "Stress-tested instalment as a percentage of the 55% TDSR income cap, before other debts (100 = cap fully used)."
              },
              "legalFees": {
                "type": "number",
                "description": "Conveyancing fees, in SGD."
              },
              "maxCpfUsage": {
                "type": "number",
                "description": "CPF the buyer(s) may use on this property, after any lease proration, in SGD."
              },
              "tenureYears": {
                "type": "number",
                "description": "Loan tenure assumed, in years (30 or 35)."
              },
              "cpfShortfall": {
                "type": "number",
                "description": "CPF missing for the non-cash part of the downpayment, in SGD (0 if none)."
              },
              "isAffordable": {
                "type": "boolean",
                "description": "True when TDSR passes at the stress rate and there is no cash or CPF shortfall."
              },
              "cashShortfall": {
                "type": "number",
                "description": "Cash still missing after the buyer's sale proceeds, in SGD (0 if none)."
              },
              "downpaymentTotal": {
                "type": "number",
                "description": "Price less the loan, in SGD."
              },
              "cpfForDownpayment": {
                "type": "number",
                "description": "CPF applied to the downpayment, in SGD."
              },
              "monthlyInstalment": {
                "type": "number",
                "description": "Monthly instalment on maxLoan at the assumed rate, in SGD."
              },
              "totalCashRequired": {
                "type": "number",
                "description": "Cash needed upfront: cash downpayment, BSD, ABSD and legal fees, in SGD."
              },
              "minCashDownpayment": {
                "type": "number",
                "description": "Part of the downpayment left to pay in cash after CPF is applied, in SGD."
              }
            },
            "description": "The option at a 30-year tenure.",
            "additionalProperties": false
          },
          "scenario35yr": {
            "type": "object",
            "required": [
              "label",
              "tenureYears",
              "maxLoan",
              "ltv",
              "monthlyInstalment",
              "downpaymentTotal",
              "minCashDownpayment",
              "cpfForDownpayment",
              "maxCpfUsage",
              "bsd",
              "absdRate",
              "absd",
              "legalFees",
              "totalCashRequired",
              "cashShortfall",
              "cpfShortfall",
              "isAffordable",
              "tdsrUsed",
              "msrUsed"
            ],
            "properties": {
              "bsd": {
                "type": "number",
                "description": "Buyer's Stamp Duty, in SGD."
              },
              "ltv": {
                "type": "number",
                "description": "Loan-to-value applied, as a decimal fraction (0.75 = 75%)."
              },
              "absd": {
                "type": "number",
                "description": "Additional Buyer’s Stamp Duty, in SGD."
              },
              "label": {
                "type": "string",
                "description": "Who buys which property in this scenario."
              },
              "maxLoan": {
                "type": "number",
                "description": "Largest loan at the LTV for this tenure and age, in SGD; 0 when the loan would run past age 75."
              },
              "msrUsed": {
                "anyOf": [
                  {
                    "type": "number"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Always null: MSR is not assessed here."
              },
              "absdRate": {
                "type": "number",
                "description": "ABSD rate applied, as a decimal fraction (0.2 = 20%)."
              },
              "tdsrUsed": {
                "type": "number",
                "description": "Stress-tested instalment as a percentage of the 55% TDSR income cap, before other debts (100 = cap fully used)."
              },
              "legalFees": {
                "type": "number",
                "description": "Conveyancing fees, in SGD."
              },
              "maxCpfUsage": {
                "type": "number",
                "description": "CPF the buyer(s) may use on this property, after any lease proration, in SGD."
              },
              "tenureYears": {
                "type": "number",
                "description": "Loan tenure assumed, in years (30 or 35)."
              },
              "cpfShortfall": {
                "type": "number",
                "description": "CPF missing for the non-cash part of the downpayment, in SGD (0 if none)."
              },
              "isAffordable": {
                "type": "boolean",
                "description": "True when TDSR passes at the stress rate and there is no cash or CPF shortfall."
              },
              "cashShortfall": {
                "type": "number",
                "description": "Cash still missing after the buyer's sale proceeds, in SGD (0 if none)."
              },
              "downpaymentTotal": {
                "type": "number",
                "description": "Price less the loan, in SGD."
              },
              "cpfForDownpayment": {
                "type": "number",
                "description": "CPF applied to the downpayment, in SGD."
              },
              "monthlyInstalment": {
                "type": "number",
                "description": "Monthly instalment on maxLoan at the assumed rate, in SGD."
              },
              "totalCashRequired": {
                "type": "number",
                "description": "Cash needed upfront: cash downpayment, BSD, ABSD and legal fees, in SGD."
              },
              "minCashDownpayment": {
                "type": "number",
                "description": "Part of the downpayment left to pay in cash after CPF is applied, in SGD."
              }
            },
            "description": "The option at a 35-year tenure.",
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "description": "Present when `buy1` was given: buying it jointly, under buyer 1 alone, or under buyer 2 alone."
    },
    "buy2Options": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "label",
          "property1",
          "property2"
        ],
        "properties": {
          "label": {
            "type": "string",
            "description": "Which buyer takes which property."
          },
          "property1": {
            "type": "object",
            "required": [
              "label",
              "scenario30yr",
              "scenario35yr"
            ],
            "properties": {
              "label": {
                "type": "string",
                "description": "Who buys, and which property."
              },
              "scenario30yr": {
                "type": "object",
                "required": [
                  "label",
                  "tenureYears",
                  "maxLoan",
                  "ltv",
                  "monthlyInstalment",
                  "downpaymentTotal",
                  "minCashDownpayment",
                  "cpfForDownpayment",
                  "maxCpfUsage",
                  "bsd",
                  "absdRate",
                  "absd",
                  "legalFees",
                  "totalCashRequired",
                  "cashShortfall",
                  "cpfShortfall",
                  "isAffordable",
                  "tdsrUsed",
                  "msrUsed"
                ],
                "properties": {
                  "bsd": {
                    "type": "number",
                    "description": "Buyer's Stamp Duty, in SGD."
                  },
                  "ltv": {
                    "type": "number",
                    "description": "Loan-to-value applied, as a decimal fraction (0.75 = 75%)."
                  },
                  "absd": {
                    "type": "number",
                    "description": "Additional Buyer’s Stamp Duty, in SGD."
                  },
                  "label": {
                    "type": "string",
                    "description": "Who buys which property in this scenario."
                  },
                  "maxLoan": {
                    "type": "number",
                    "description": "Largest loan at the LTV for this tenure and age, in SGD; 0 when the loan would run past age 75."
                  },
                  "msrUsed": {
                    "anyOf": [
                      {
                        "type": "number"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Always null: MSR is not assessed here."
                  },
                  "absdRate": {
                    "type": "number",
                    "description": "ABSD rate applied, as a decimal fraction (0.2 = 20%)."
                  },
                  "tdsrUsed": {
                    "type": "number",
                    "description": "Stress-tested instalment as a percentage of the 55% TDSR income cap, before other debts (100 = cap fully used)."
                  },
                  "legalFees": {
                    "type": "number",
                    "description": "Conveyancing fees, in SGD."
                  },
                  "maxCpfUsage": {
                    "type": "number",
                    "description": "CPF the buyer(s) may use on this property, after any lease proration, in SGD."
                  },
                  "tenureYears": {
                    "type": "number",
                    "description": "Loan tenure assumed, in years (30 or 35)."
                  },
                  "cpfShortfall": {
                    "type": "number",
                    "description": "CPF missing for the non-cash part of the downpayment, in SGD (0 if none)."
                  },
                  "isAffordable": {
                    "type": "boolean",
                    "description": "True when TDSR passes at the stress rate and there is no cash or CPF shortfall."
                  },
                  "cashShortfall": {
                    "type": "number",
                    "description": "Cash still missing after the buyer's sale proceeds, in SGD (0 if none)."
                  },
                  "downpaymentTotal": {
                    "type": "number",
                    "description": "Price less the loan, in SGD."
                  },
                  "cpfForDownpayment": {
                    "type": "number",
                    "description": "CPF applied to the downpayment, in SGD."
                  },
                  "monthlyInstalment": {
                    "type": "number",
                    "description": "Monthly instalment on maxLoan at the assumed rate, in SGD."
                  },
                  "totalCashRequired": {
                    "type": "number",
                    "description": "Cash needed upfront: cash downpayment, BSD, ABSD and legal fees, in SGD."
                  },
                  "minCashDownpayment": {
                    "type": "number",
                    "description": "Part of the downpayment left to pay in cash after CPF is applied, in SGD."
                  }
                },
                "description": "The option at a 30-year tenure.",
                "additionalProperties": false
              },
              "scenario35yr": {
                "type": "object",
                "required": [
                  "label",
                  "tenureYears",
                  "maxLoan",
                  "ltv",
                  "monthlyInstalment",
                  "downpaymentTotal",
                  "minCashDownpayment",
                  "cpfForDownpayment",
                  "maxCpfUsage",
                  "bsd",
                  "absdRate",
                  "absd",
                  "legalFees",
                  "totalCashRequired",
                  "cashShortfall",
                  "cpfShortfall",
                  "isAffordable",
                  "tdsrUsed",
                  "msrUsed"
                ],
                "properties": {
                  "bsd": {
                    "type": "number",
                    "description": "Buyer's Stamp Duty, in SGD."
                  },
                  "ltv": {
                    "type": "number",
                    "description": "Loan-to-value applied, as a decimal fraction (0.75 = 75%)."
                  },
                  "absd": {
                    "type": "number",
                    "description": "Additional Buyer’s Stamp Duty, in SGD."
                  },
                  "label": {
                    "type": "string",
                    "description": "Who buys which property in this scenario."
                  },
                  "maxLoan": {
                    "type": "number",
                    "description": "Largest loan at the LTV for this tenure and age, in SGD; 0 when the loan would run past age 75."
                  },
                  "msrUsed": {
                    "anyOf": [
                      {
                        "type": "number"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Always null: MSR is not assessed here."
                  },
                  "absdRate": {
                    "type": "number",
                    "description": "ABSD rate applied, as a decimal fraction (0.2 = 20%)."
                  },
                  "tdsrUsed": {
                    "type": "number",
                    "description": "Stress-tested instalment as a percentage of the 55% TDSR income cap, before other debts (100 = cap fully used)."
                  },
                  "legalFees": {
                    "type": "number",
                    "description": "Conveyancing fees, in SGD."
                  },
                  "maxCpfUsage": {
                    "type": "number",
                    "description": "CPF the buyer(s) may use on this property, after any lease proration, in SGD."
                  },
                  "tenureYears": {
                    "type": "number",
                    "description": "Loan tenure assumed, in years (30 or 35)."
                  },
                  "cpfShortfall": {
                    "type": "number",
                    "description": "CPF missing for the non-cash part of the downpayment, in SGD (0 if none)."
                  },
                  "isAffordable": {
                    "type": "boolean",
                    "description": "True when TDSR passes at the stress rate and there is no cash or CPF shortfall."
                  },
                  "cashShortfall": {
                    "type": "number",
                    "description": "Cash still missing after the buyer's sale proceeds, in SGD (0 if none)."
                  },
                  "downpaymentTotal": {
                    "type": "number",
                    "description": "Price less the loan, in SGD."
                  },
                  "cpfForDownpayment": {
                    "type": "number",
                    "description": "CPF applied to the downpayment, in SGD."
                  },
                  "monthlyInstalment": {
                    "type": "number",
                    "description": "Monthly instalment on maxLoan at the assumed rate, in SGD."
                  },
                  "totalCashRequired": {
                    "type": "number",
                    "description": "Cash needed upfront: cash downpayment, BSD, ABSD and legal fees, in SGD."
                  },
                  "minCashDownpayment": {
                    "type": "number",
                    "description": "Part of the downpayment left to pay in cash after CPF is applied, in SGD."
                  }
                },
                "description": "The option at a 35-year tenure.",
                "additionalProperties": false
              }
            },
            "description": "Property 1 under its assigned buyer.",
            "additionalProperties": false
          },
          "property2": {
            "type": "object",
            "required": [
              "label",
              "scenario30yr",
              "scenario35yr"
            ],
            "properties": {
              "label": {
                "type": "string",
                "description": "Who buys, and which property."
              },
              "scenario30yr": {
                "type": "object",
                "required": [
                  "label",
                  "tenureYears",
                  "maxLoan",
                  "ltv",
                  "monthlyInstalment",
                  "downpaymentTotal",
                  "minCashDownpayment",
                  "cpfForDownpayment",
                  "maxCpfUsage",
                  "bsd",
                  "absdRate",
                  "absd",
                  "legalFees",
                  "totalCashRequired",
                  "cashShortfall",
                  "cpfShortfall",
                  "isAffordable",
                  "tdsrUsed",
                  "msrUsed"
                ],
                "properties": {
                  "bsd": {
                    "type": "number",
                    "description": "Buyer's Stamp Duty, in SGD."
                  },
                  "ltv": {
                    "type": "number",
                    "description": "Loan-to-value applied, as a decimal fraction (0.75 = 75%)."
                  },
                  "absd": {
                    "type": "number",
                    "description": "Additional Buyer’s Stamp Duty, in SGD."
                  },
                  "label": {
                    "type": "string",
                    "description": "Who buys which property in this scenario."
                  },
                  "maxLoan": {
                    "type": "number",
                    "description": "Largest loan at the LTV for this tenure and age, in SGD; 0 when the loan would run past age 75."
                  },
                  "msrUsed": {
                    "anyOf": [
                      {
                        "type": "number"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Always null: MSR is not assessed here."
                  },
                  "absdRate": {
                    "type": "number",
                    "description": "ABSD rate applied, as a decimal fraction (0.2 = 20%)."
                  },
                  "tdsrUsed": {
                    "type": "number",
                    "description": "Stress-tested instalment as a percentage of the 55% TDSR income cap, before other debts (100 = cap fully used)."
                  },
                  "legalFees": {
                    "type": "number",
                    "description": "Conveyancing fees, in SGD."
                  },
                  "maxCpfUsage": {
                    "type": "number",
                    "description": "CPF the buyer(s) may use on this property, after any lease proration, in SGD."
                  },
                  "tenureYears": {
                    "type": "number",
                    "description": "Loan tenure assumed, in years (30 or 35)."
                  },
                  "cpfShortfall": {
                    "type": "number",
                    "description": "CPF missing for the non-cash part of the downpayment, in SGD (0 if none)."
                  },
                  "isAffordable": {
                    "type": "boolean",
                    "description": "True when TDSR passes at the stress rate and there is no cash or CPF shortfall."
                  },
                  "cashShortfall": {
                    "type": "number",
                    "description": "Cash still missing after the buyer's sale proceeds, in SGD (0 if none)."
                  },
                  "downpaymentTotal": {
                    "type": "number",
                    "description": "Price less the loan, in SGD."
                  },
                  "cpfForDownpayment": {
                    "type": "number",
                    "description": "CPF applied to the downpayment, in SGD."
                  },
                  "monthlyInstalment": {
                    "type": "number",
                    "description": "Monthly instalment on maxLoan at the assumed rate, in SGD."
                  },
                  "totalCashRequired": {
                    "type": "number",
                    "description": "Cash needed upfront: cash downpayment, BSD, ABSD and legal fees, in SGD."
                  },
                  "minCashDownpayment": {
                    "type": "number",
                    "description": "Part of the downpayment left to pay in cash after CPF is applied, in SGD."
                  }
                },
                "description": "The option at a 30-year tenure.",
                "additionalProperties": false
              },
              "scenario35yr": {
                "type": "object",
                "required": [
                  "label",
                  "tenureYears",
                  "maxLoan",
                  "ltv",
                  "monthlyInstalment",
                  "downpaymentTotal",
                  "minCashDownpayment",
                  "cpfForDownpayment",
                  "maxCpfUsage",
                  "bsd",
                  "absdRate",
                  "absd",
                  "legalFees",
                  "totalCashRequired",
                  "cashShortfall",
                  "cpfShortfall",
                  "isAffordable",
                  "tdsrUsed",
                  "msrUsed"
                ],
                "properties": {
                  "bsd": {
                    "type": "number",
                    "description": "Buyer's Stamp Duty, in SGD."
                  },
                  "ltv": {
                    "type": "number",
                    "description": "Loan-to-value applied, as a decimal fraction (0.75 = 75%)."
                  },
                  "absd": {
                    "type": "number",
                    "description": "Additional Buyer’s Stamp Duty, in SGD."
                  },
                  "label": {
                    "type": "string",
                    "description": "Who buys which property in this scenario."
                  },
                  "maxLoan": {
                    "type": "number",
                    "description": "Largest loan at the LTV for this tenure and age, in SGD; 0 when the loan would run past age 75."
                  },
                  "msrUsed": {
                    "anyOf": [
                      {
                        "type": "number"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Always null: MSR is not assessed here."
                  },
                  "absdRate": {
                    "type": "number",
                    "description": "ABSD rate applied, as a decimal fraction (0.2 = 20%)."
                  },
                  "tdsrUsed": {
                    "type": "number",
                    "description": "Stress-tested instalment as a percentage of the 55% TDSR income cap, before other debts (100 = cap fully used)."
                  },
                  "legalFees": {
                    "type": "number",
                    "description": "Conveyancing fees, in SGD."
                  },
                  "maxCpfUsage": {
                    "type": "number",
                    "description": "CPF the buyer(s) may use on this property, after any lease proration, in SGD."
                  },
                  "tenureYears": {
                    "type": "number",
                    "description": "Loan tenure assumed, in years (30 or 35)."
                  },
                  "cpfShortfall": {
                    "type": "number",
                    "description": "CPF missing for the non-cash part of the downpayment, in SGD (0 if none)."
                  },
                  "isAffordable": {
                    "type": "boolean",
                    "description": "True when TDSR passes at the stress rate and there is no cash or CPF shortfall."
                  },
                  "cashShortfall": {
                    "type": "number",
                    "description": "Cash still missing after the buyer's sale proceeds, in SGD (0 if none)."
                  },
                  "downpaymentTotal": {
                    "type": "number",
                    "description": "Price less the loan, in SGD."
                  },
                  "cpfForDownpayment": {
                    "type": "number",
                    "description": "CPF applied to the downpayment, in SGD."
                  },
                  "monthlyInstalment": {
                    "type": "number",
                    "description": "Monthly instalment on maxLoan at the assumed rate, in SGD."
                  },
                  "totalCashRequired": {
                    "type": "number",
                    "description": "Cash needed upfront: cash downpayment, BSD, ABSD and legal fees, in SGD."
                  },
                  "minCashDownpayment": {
                    "type": "number",
                    "description": "Part of the downpayment left to pay in cash after CPF is applied, in SGD."
                  }
                },
                "description": "The option at a 35-year tenure.",
                "additionalProperties": false
              }
            },
            "description": "Property 2 under its assigned buyer.",
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "description": "Present when both `buy2Property1` and `buy2Property2` were given: each buyer buys one property, in both pairings."
    },
    "buyer1Funds": {
      "type": "object",
      "required": [
        "shares",
        "cpfFromSale",
        "existingCpfOA",
        "totalCpfAvailable",
        "totalCashProceeds"
      ],
      "properties": {
        "shares": {
          "type": "number",
          "description": "This buyer's ownership share of the sold property, in percent."
        },
        "cpfFromSale": {
          "type": "number",
          "description": "CPF refunded to this buyer from the sale, in SGD."
        },
        "existingCpfOA": {
          "type": "number",
          "description": "CPF OA balance the buyer already had, in SGD."
        },
        "totalCashProceeds": {
          "type": "number",
          "description": "This buyer's share of the cash proceeds, in SGD."
        },
        "totalCpfAvailable": {
          "type": "number",
          "description": "CPF available for the next purchase (refund plus existing OA), in SGD."
        }
      },
      "description": "Buyer 1's cash and CPF after the sale.",
      "additionalProperties": false
    },
    "buyer2Funds": {
      "type": "object",
      "required": [
        "shares",
        "cpfFromSale",
        "existingCpfOA",
        "totalCpfAvailable",
        "totalCashProceeds"
      ],
      "properties": {
        "shares": {
          "type": "number",
          "description": "This buyer's ownership share of the sold property, in percent."
        },
        "cpfFromSale": {
          "type": "number",
          "description": "CPF refunded to this buyer from the sale, in SGD."
        },
        "existingCpfOA": {
          "type": "number",
          "description": "CPF OA balance the buyer already had, in SGD."
        },
        "totalCashProceeds": {
          "type": "number",
          "description": "This buyer's share of the cash proceeds, in SGD."
        },
        "totalCpfAvailable": {
          "type": "number",
          "description": "CPF available for the next purchase (refund plus existing OA), in SGD."
        }
      },
      "description": "Buyer 2's cash and CPF after the sale.",
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}
analyze_sell_one_buy_two annotations
{
  "title": "Sell-one-buy-two analysis",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
check_eligibility — Eligibility checker (TDSR / MSR)
Advertised by the server during the last successful anonymous check.
Advertised read-only

Run the regulatory affordability gate for one main borrower and up to three joint borrowers. Given each borrower's income, rental, pledged/unpledged assets and liabilities plus a proposed loan, returns the qualifying income (after MAS haircuts), the TDSR and MSR for the proposed loan, the income-weighted average age (IWAA), and the maximum loan under both HDB/EC and private property rules. Use this upstream of `recommend_packages` to confirm what a borrower can qualify for.

check_eligibility input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "borrowers",
    "proposedLoanAmount"
  ],
  "properties": {
    "asOfDate": {
      "type": "string",
      "description": "Reference date (ISO YYYY-MM-DD) for age and IWAA; defaults to today."
    },
    "borrowers": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "age": {
            "type": "number",
            "maximum": 120,
            "minimum": 0,
            "description": "Borrower age in years. When supplied it is used directly and dateOfBirth is ignored."
          },
          "annualNoa": {
            "type": "number",
            "minimum": 0,
            "description": "Annual income per the IRAS Notice of Assessment, in SGD. Only used when employedTwoYears is true; recognised variable income is then max(NOA − basic×12, annualVariableIncome)."
          },
          "dateOfBirth": {
            "type": "string",
            "description": "Borrower date of birth, e.g. \"1990-05-14\"; must not be in the future. Gives the age at `asOfDate` when `age` is not supplied. A borrower with neither is treated as age unknown and left out of the IWAA."
          },
          "liabilities": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string",
                  "default": "",
                  "maxLength": 60,
                  "description": "What the liability is, e.g. \"car loan\" or \"credit card\". For display only."
                },
                "monthlyAmount": {
                  "type": "number",
                  "default": 0,
                  "minimum": 0,
                  "description": "Monthly repayment for this liability, in SGD."
                }
              }
            },
            "default": [],
            "maxItems": 3,
            "description": "Up to three monthly liability lines."
          },
          "pledgedFunds": {
            "type": "number",
            "default": 0,
            "minimum": 0,
            "description": "Eligible financial assets pledged ≥4 years, in SGD. Amortised over 48 months at 100%."
          },
          "unpledgedFunds": {
            "type": "number",
            "default": 0,
            "minimum": 0,
            "description": "Eligible financial assets not pledged, in SGD. Amortised over 48 months at 30%."
          },
          "employedTwoYears": {
            "type": "boolean",
            "description": "True if the borrower has ≥2 years with the current employer, which permits using the NOA for variable income."
          },
          "monthlyBasicIncome": {
            "type": "number",
            "default": 0,
            "minimum": 0,
            "description": "Fixed monthly salary before deductions, in SGD; counts in full."
          },
          "monthlyRentalIncome": {
            "type": "number",
            "default": 0,
            "minimum": 0,
            "description": "Monthly rental income (requires a valid tenancy agreement), in SGD. Recognised at 70%."
          },
          "annualVariableIncome": {
            "type": "number",
            "default": 0,
            "minimum": 0,
            "description": "Yearly bonus, commission and allowances, in SGD; 70% of it, spread over 12 months, counts."
          }
        }
      },
      "maxItems": 4,
      "minItems": 1,
      "description": "The main borrower followed by any joint borrowers; their qualifying incomes and liabilities are pooled."
    },
    "propertyType": {
      "enum": [
        "hdb",
        "ec",
        "private"
      ],
      "type": "string",
      "default": "private",
      "description": "Property type: hdb, ec or private. Sets the maximum loan tenure (35y private and EC, 30y HDB) and the lower-LTV-tier triggers. EC also carries MSR, like HDB."
    },
    "targetLtvCap": {
      "type": "number",
      "maximum": 0.9,
      "minimum": 0,
      "description": "Optional manual LTV cap for the target loan, as a decimal (e.g. 0.6 = 60%). When set and below the applicable LTV, toReachTarget is sized at this lower LTV so the loan and unlock figures reflect a smaller loan the borrower chooses to take."
    },
    "ageConvention": {
      "enum": [
        "birth-date",
        "birth-year"
      ],
      "type": "string",
      "description": "How borrower age is computed; defaults to birth-date. The tRPC router resolves it per bank from the bank slug."
    },
    "benchmarkRate": {
      "type": "number",
      "default": 4,
      "maximum": 15,
      "minimum": 0.1,
      "description": "MAS medium-term stress rate, as a percentage (e.g. 4.0 = 4%, not 0.04)."
    },
    "loanTenureYears": {
      "type": "integer",
      "default": 25,
      "maximum": 35,
      "minimum": 1,
      "description": "Loan tenure being tested, in years; checked against the longest tenure these borrowers can get."
    },
    "combinationSplit": {
      "type": "number",
      "maximum": 1,
      "minimum": 0,
      "description": "For the pledge/show combination: fraction of the income gap covered by pledged funds; the remainder is covered by show funds. Defaults to 0.5."
    },
    "targetLoanAmount": {
      "type": "number",
      "minimum": 0,
      "description": "Optional target loan amount, in SGD. With targetPurchasePrice it sets the LTV (loan / price, capped at the applicable LTV) and takes precedence over targetLtvCap. Without a price, the price is derived as the loan divided by the applicable LTV. Either way toReachTarget is produced."
    },
    "proposedLoanAmount": {
      "type": "number",
      "minimum": 0,
      "description": "The loan amount being tested, in SGD."
    },
    "targetPurchasePrice": {
      "type": "number",
      "minimum": 0,
      "description": "Optional target purchase price, in SGD. When set, the result includes toReachTarget: the loan needed at the first-loan LTV and the eligible funds to reach it."
    },
    "existingHousingLoans": {
      "type": "integer",
      "maximum": 10,
      "minimum": 0,
      "description": "Number of outstanding housing loans held. Selects the LTV tier (0 -> full, 1 -> second, 2+ -> third). Defaults to 0 when omitted."
    }
  }
}
check_eligibility output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "borrowers",
    "household",
    "monthlyInstalment",
    "msr",
    "tdsr",
    "maxLoanHdbEc",
    "maxLoanPrivate",
    "bindingConstraint",
    "qualifiesHdbEc",
    "qualifiesPrivate",
    "proposedLoanAmount",
    "benchmarkRate",
    "propertyType",
    "tenure",
    "ltv",
    "fundsToQualify",
    "toReachTarget",
    "priceIf",
    "applicableLtv",
    "applicableMinCashPercent",
    "maxPrice",
    "warnings",
    "insufficientIncome"
  ],
  "properties": {
    "ltv": {
      "type": "object",
      "required": [
        "lowerTier",
        "reasons"
      ],
      "properties": {
        "reasons": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Which trigger(s) put the loan into the lower LTV tier (tenure or age 65)."
        },
        "lowerTier": {
          "type": "boolean",
          "description": "True if the loan falls into the reduced-LTV tier. LTV itself is not computed here."
        }
      },
      "description": "Lower-LTV-tier flags, informational only — LTV is out of scope for this checker.",
      "additionalProperties": false
    },
    "msr": {
      "type": "object",
      "required": [
        "ratio",
        "cap",
        "pass"
      ],
      "properties": {
        "cap": {
          "type": "number",
          "description": "Regulatory cap as a decimal fraction (0.30 = 30%)."
        },
        "pass": {
          "type": "boolean",
          "description": "True if the ratio is at or below the cap."
        },
        "ratio": {
          "type": "number",
          "description": "Computed ratio as a decimal fraction (0.182 = 18.20%)."
        }
      },
      "description": "Mortgage Servicing Ratio (HDB/EC), capped at 30%.",
      "additionalProperties": false
    },
    "tdsr": {
      "type": "object",
      "required": [
        "ratio",
        "cap",
        "pass"
      ],
      "properties": {
        "cap": {
          "type": "number",
          "description": "Regulatory cap as a decimal fraction (0.30 = 30%)."
        },
        "pass": {
          "type": "boolean",
          "description": "True if the ratio is at or below the cap."
        },
        "ratio": {
          "type": "number",
          "description": "Computed ratio as a decimal fraction (0.182 = 18.20%)."
        }
      },
      "description": "Total Debt Servicing Ratio (all property), capped at 55%.",
      "additionalProperties": false
    },
    "tenure": {
      "type": "object",
      "required": [
        "requestedYears",
        "maxYears",
        "maxLoanTenorYears",
        "maxLtvYears",
        "withinCap",
        "effectiveYears",
        "limitedBy"
      ],
      "properties": {
        "maxYears": {
          "type": "number",
          "description": "Maximum loan tenure for these borrowers: the regulatory cap (35y private and EC, 30y HDB) limited so the loan is repaid by age 75, i.e. min(cap, 75 - IWAA), in years. Equal to maxLoanTenorYears."
        },
        "limitedBy": {
          "anyOf": [
            {
              "enum": [
                "age",
                "cap"
              ],
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "description": "Why effectiveYears is below the requested tenure: \"age\" when the loan must be repaid by 75, \"cap\" when the property type's regulatory maximum binds. Null when the requested tenure is used as is."
        },
        "withinCap": {
          "type": "boolean",
          "description": "True if the requested tenure is within the maximum loan tenure."
        },
        "maxLtvYears": {
          "type": "number",
          "description": "Longest loan tenure that still qualifies for the top LTV tier: min(LTV tenure cap of 30y private and EC / 25y HDB, 65 - IWAA), in whole years."
        },
        "effectiveYears": {
          "type": "number",
          "description": "Tenure the result is actually calculated at: the requested tenure, lowered to maxYears when it exceeds it, in years."
        },
        "requestedYears": {
          "type": "number",
          "description": "Proposed tenure entered by the user, in years."
        },
        "maxLoanTenorYears": {
          "type": "number",
          "description": "Tenor the maximum loan is sized at: min(regulatory cap, 75 - IWAA), in whole years. Beyond age 75 the LTV is 0 so the loan is not financeable."
        }
      },
      "additionalProperties": false
    },
    "priceIf": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "loans",
          "ltv",
          "maxPrice"
        ],
        "properties": {
          "ltv": {
            "type": "number",
            "description": "Applicable LTV for that loan count, as a decimal (0.75 = 75%)."
          },
          "loans": {
            "type": "number",
            "description": "Number of housing loans the borrower would hold (1, 2 or 3+)."
          },
          "maxPrice": {
            "type": "number",
            "description": "Max property price at the max loan for the property type (MSR-bound for HDB/EC) and this LTV tier, in SGD."
          }
        },
        "additionalProperties": false
      },
      "description": "Max purchase price by number of existing housing loans; the LTV tier (full / reduced / none) follows whether the loan extends past age 65 / 75."
    },
    "maxPrice": {
      "type": "number",
      "description": "Max property price at the applicable LTV for the entered housing-loan count, using the max loan for the property type (MSR-bound for HDB/EC), in SGD."
    },
    "warnings": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Advisory messages (insufficient income, tenure over the maximum)."
    },
    "borrowers": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "age",
          "recognisedAssetIncome",
          "qualifyingIncome",
          "totalLiabilities",
          "incomeBreakdown"
        ],
        "properties": {
          "age": {
            "type": "number",
            "description": "Borrower age in years (fractional)."
          },
          "incomeBreakdown": {
            "type": "object",
            "required": [
              "basic",
              "variable",
              "rental",
              "funds"
            ],
            "properties": {
              "basic": {
                "type": "number",
                "description": "Monthly basic income, recognised at 100%, in SGD."
              },
              "funds": {
                "type": "number",
                "description": "Recognised monthly income from pledged/show funds (amortised over 48 months), in SGD."
              },
              "rental": {
                "type": "number",
                "description": "Monthly rental income recognised at 70%, in SGD."
              },
              "variable": {
                "type": "number",
                "description": "Monthly variable income (NOA/variable) recognised at 70%, then /12, in SGD."
              }
            },
            "description": "The monthly components of qualifyingIncome (basic + variable + rental + funds), each rounded to the cent.",
            "additionalProperties": false
          },
          "qualifyingIncome": {
            "type": "number",
            "description": "Qualifying monthly income after MAS haircuts, in SGD."
          },
          "totalLiabilities": {
            "type": "number",
            "description": "Total monthly liabilities for this borrower, in SGD."
          },
          "recognisedAssetIncome": {
            "type": "number",
            "description": "Recognised monthly income from assets and rental (rental + amortised pledged/unpledged), in SGD."
          }
        },
        "additionalProperties": false
      },
      "description": "Per-borrower breakdown."
    },
    "household": {
      "type": "object",
      "required": [
        "qualifyingIncome",
        "totalLiabilities",
        "iwaa"
      ],
      "properties": {
        "iwaa": {
          "type": "number",
          "description": "Income-Weighted Average Age (years); 0 when income is zero."
        },
        "qualifyingIncome": {
          "type": "number",
          "description": "Total household qualifying monthly income, in SGD."
        },
        "totalLiabilities": {
          "type": "number",
          "description": "Total household monthly liabilities, in SGD."
        }
      },
      "additionalProperties": false
    },
    "maxLoanHdbEc": {
      "type": "number",
      "description": "Maximum loan under HDB/EC rules: the lower of the MSR and TDSR caps, in SGD."
    },
    "propertyType": {
      "enum": [
        "hdb",
        "ec",
        "private"
      ],
      "type": "string",
      "description": "Property type the tenure and LTV-tier rules were applied for."
    },
    "applicableLtv": {
      "type": "number",
      "description": "The LTV that applies for the entered number of existing housing loans, as a decimal (0.75 = 75%)."
    },
    "benchmarkRate": {
      "type": "number",
      "description": "The benchmark stress rate applied, as a percentage."
    },
    "toReachTarget": {
      "anyOf": [
        {
          "type": "object",
          "required": [
            "targetPurchasePrice",
            "priceDerived",
            "loanCappedByLtv",
            "loanRequired",
            "additionalLoan",
            "incomeGap",
            "pledgedFundsRequired",
            "unpledgedFundsRequired",
            "combination"
          ],
          "properties": {
            "incomeGap": {
              "type": "number",
              "description": "Extra monthly qualifying income needed to service the additional loan, in SGD."
            },
            "combination": {
              "type": "object",
              "required": [
                "splitPledgedPercent",
                "pledgedFundsRequired",
                "showFundsRequired"
              ],
              "properties": {
                "showFundsRequired": {
                  "type": "number",
                  "description": "Show funds for the remaining portion, in SGD."
                },
                "splitPledgedPercent": {
                  "type": "number",
                  "description": "Fraction of the gap covered by pledged funds (0–100)."
                },
                "pledgedFundsRequired": {
                  "type": "number",
                  "description": "Pledged funds for the pledged portion, in SGD."
                }
              },
              "description": "A pledge/show mix that reaches the target, split by combinationSplit.",
              "additionalProperties": false
            },
            "loanRequired": {
              "type": "number",
              "description": "Loan needed for the target at the first-loan LTV (price × LTV), in SGD."
            },
            "priceDerived": {
              "type": "boolean",
              "description": "True when no price was supplied and targetPurchasePrice was derived as targetLoanAmount / applicable LTV."
            },
            "additionalLoan": {
              "type": "number",
              "description": "Loan above the borrower’s current maximum that must be bridged, in SGD (0 if already reachable)."
            },
            "loanCappedByLtv": {
              "type": "boolean",
              "description": "True when the supplied targetLoanAmount exceeds price × applicable LTV, so loanRequired was lowered to that cap."
            },
            "targetPurchasePrice": {
              "type": "number",
              "description": "The target purchase price, in SGD: as supplied, or derived from the target loan."
            },
            "pledgedFundsRequired": {
              "type": "number",
              "description": "Pledged eligible funds to reach the target on their own, in SGD."
            },
            "unpledgedFundsRequired": {
              "type": "number",
              "description": "Show (unpledged) funds to reach the target on their own, in SGD."
            }
          },
          "additionalProperties": false
        },
        {
          "type": "null"
        }
      ],
      "description": "Present only when targetPurchasePrice is supplied: the funds needed to reach that price."
    },
    "fundsToQualify": {
      "type": "object",
      "required": [
        "qualifies",
        "incomeGap",
        "pledgedFundsRequired",
        "unpledgedFundsRequired",
        "combination"
      ],
      "properties": {
        "incomeGap": {
          "type": "number",
          "description": "Additional monthly qualifying income needed to qualify, in SGD (0 if it does)."
        },
        "qualifies": {
          "type": "boolean",
          "description": "True if the proposed loan already passes for the selected property type."
        },
        "combination": {
          "type": "object",
          "required": [
            "splitPledgedPercent",
            "pledgedFundsRequired",
            "showFundsRequired"
          ],
          "properties": {
            "showFundsRequired": {
              "type": "number",
              "description": "Show (unpledged) funds for the remaining portion of the gap, in SGD."
            },
            "splitPledgedPercent": {
              "type": "number",
              "description": "Fraction of the income gap covered by pledged funds (0–100)."
            },
            "pledgedFundsRequired": {
              "type": "number",
              "description": "Pledged funds for the pledged portion of the gap, in SGD."
            }
          },
          "description": "A mix of pledged and show funds that closes the gap, split by combinationSplit.",
          "additionalProperties": false
        },
        "pledgedFundsRequired": {
          "type": "number",
          "description": "Additional pledged eligible funds needed to close the gap, in SGD."
        },
        "unpledgedFundsRequired": {
          "type": "number",
          "description": "Additional unpledged eligible funds (\"show funds\") needed to close the gap, in SGD."
        }
      },
      "description": "When the loan does not qualify, the extra eligible funds that would make it qualify.",
      "additionalProperties": false
    },
    "maxLoanPrivate": {
      "type": "number",
      "description": "Maximum loan under private property rules (TDSR-bound), in SGD."
    },
    "qualifiesHdbEc": {
      "type": "boolean",
      "description": "True if the proposed loan passes both MSR and TDSR (HDB/EC)."
    },
    "qualifiesPrivate": {
      "type": "boolean",
      "description": "True if the proposed loan passes TDSR (private property)."
    },
    "bindingConstraint": {
      "enum": [
        "msr",
        "tdsr"
      ],
      "type": "string",
      "description": "Which rule produces the lower maximum loan."
    },
    "monthlyInstalment": {
      "type": "number",
      "description": "Monthly instalment on the proposed loan at the benchmark stress rate, in SGD."
    },
    "insufficientIncome": {
      "type": "boolean",
      "description": "True if total household income is zero, so ratios cannot be assessed."
    },
    "proposedLoanAmount": {
      "type": "number",
      "description": "The proposed loan amount that was tested, in SGD."
    },
    "applicableMinCashPercent": {
      "type": "number",
      "description": "Mandatory cash downpayment for the applicable LTV tier, as a decimal (0.05 = 5%): 5% at the full tier, 10% at the reduced tier, 25% with an outstanding housing loan."
    }
  },
  "additionalProperties": false
}
check_eligibility annotations
{
  "title": "Eligibility checker (TDSR / MSR)",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
analyze_decoupling — Decoupling analysis
Advertised by the server during the last successful anonymous check.
Advertised read-only

Analyse decoupling: the cost versus benefit of transferring one co-owner's share so a borrower is freed up for another purchase (including stamp duty).

analyze_decoupling input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "valuation",
    "seller",
    "buyer"
  ],
  "properties": {
    "buyer": {
      "type": "object",
      "properties": {
        "age": {
          "type": "integer",
          "default": 35,
          "maximum": 80,
          "minimum": 21,
          "description": "Buyer's age in years; limits the loan tenure (age plus tenure at most 65), the LTV tier and, on a leasehold, the CPF usable."
        },
        "cpfInOA": {
          "type": "number",
          "default": 0,
          "minimum": 0,
          "description": "Buyer's CPF Ordinary Account balance available for the downpayment, in SGD."
        },
        "legalFees": {
          "type": "number",
          "default": 3000,
          "minimum": 0,
          "description": "Buyer's conveyancing fees, in SGD; part of the buyer's cash required."
        },
        "citizenship": {
          "enum": [
            "singaporean",
            "pr",
            "foreigner"
          ],
          "type": "string",
          "default": "singaporean",
          "description": "Buyer's residency status; sets the ABSD on taking over the share and in the comparison `options`."
        },
        "financingPercent": {
          "enum": [
            75,
            55,
            45,
            25
          ],
          "type": "number",
          "description": "Loan-to-value percentage to borrow against the seller's share: 75 and 55 are first-housing-loan tiers, 45 and 25 second-loan tiers. It can only lower what the buyer's age and tenure allow; omit to use the highest allowed."
        },
        "loanInterestRate": {
          "type": "number",
          "default": 2.6,
          "maximum": 15,
          "minimum": 0.1,
          "description": "Annual rate assumed for the buyer's new loan, as a percentage (e.g. 2.6 = 2.6%); sets the monthly instalment."
        },
        "remainingLeaseYears": {
          "type": "number",
          "maximum": 99,
          "minimum": 0,
          "description": "Remaining lease on the property, in years; caps the loan tenure and, when short, the CPF the buyer may use. Omit for freehold."
        }
      },
      "description": "The co-owner buying out the share and refinancing."
    },
    "seller": {
      "type": "object",
      "properties": {
        "shares": {
          "type": "number",
          "default": 50,
          "maximum": 100,
          "minimum": 0,
          "description": "Seller's ownership share, in percent, which the buyer takes over. The buyer already holds the rest."
        },
        "legalFees": {
          "type": "number",
          "default": 3000,
          "minimum": 0,
          "description": "Seller's conveyancing fees, in SGD; deducted from the seller's proceeds."
        },
        "citizenship": {
          "enum": [
            "singaporean",
            "pr",
            "foreigner"
          ],
          "type": "string",
          "default": "singaporean",
          "description": "Seller's residency status; sets their ABSD rate in the purchase comparison `options`."
        },
        "sellerStampDuty": {
          "type": "number",
          "default": 0,
          "minimum": 0,
          "description": "Seller's Stamp Duty on the transfer, in SGD; deducted from the seller's proceeds."
        },
        "cpfUsageInterest": {
          "type": "number",
          "default": 0,
          "minimum": 0,
          "description": "CPF the seller used on the property plus accrued interest, in SGD; refunded to their CPF out of the seller's proceeds."
        }
      },
      "description": "The co-owner transferring out their share."
    },
    "valuation": {
      "type": "number",
      "minimum": 0,
      "description": "Current market value of the whole property, in SGD; each party's share is valued pro rata."
    },
    "outstandingLoan": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Loan still owed on the whole property, in SGD; split between the parties by share."
    },
    "newPropertyPrice": {
      "type": "number",
      "minimum": 0,
      "description": "Price of a subsequent property purchase the freed-up buyer plans, in SGD."
    }
  }
}
analyze_decoupling output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "summary",
    "seller",
    "buyer",
    "initialPayment",
    "stages",
    "options"
  ],
  "properties": {
    "buyer": {
      "type": "object",
      "required": [
        "refinancedLoan",
        "loanForSellerShares",
        "loanForSellerSharesLtvPercent",
        "maxLoanFromBank",
        "maxTenureYears",
        "maxCpfUsage",
        "cashRequired"
      ],
      "properties": {
        "maxCpfUsage": {
          "type": "number",
          "description": "CPF the buyer may use towards the share, in SGD."
        },
        "cashRequired": {
          "type": "number",
          "description": "Downpayment on the share left for cash after CPF, in SGD."
        },
        "maxTenureYears": {
          "type": "number",
          "description": "Loan tenure used, in years."
        },
        "refinancedLoan": {
          "type": "number",
          "description": "Buyer's own share of the outstanding loan, refinanced, in SGD."
        },
        "maxLoanFromBank": {
          "type": "number",
          "description": "Total loan: refinanced plus new, in SGD."
        },
        "loanForSellerShares": {
          "type": "number",
          "description": "New loan to buy the seller's share, in SGD."
        },
        "loanForSellerSharesLtvPercent": {
          "type": "number",
          "description": "LTV applied to that new loan, in percent (75 = 75%)."
        }
      },
      "description": "The buyer's financing of the share.",
      "additionalProperties": false
    },
    "seller": {
      "type": "object",
      "required": [
        "valuation",
        "outstandingLoan",
        "cpfUsageInterest",
        "sellerStampDuty",
        "legalFees",
        "cashProceed"
      ],
      "properties": {
        "legalFees": {
          "type": "number",
          "description": "Seller's conveyancing fees, in SGD."
        },
        "valuation": {
          "type": "number",
          "description": "Value of the seller's share, in SGD."
        },
        "cashProceed": {
          "type": "number",
          "description": "Value of the share less loan, CPF refund, SSD and legal fees, in SGD."
        },
        "outstandingLoan": {
          "type": "number",
          "description": "Seller's share of the outstanding loan, in SGD."
        },
        "sellerStampDuty": {
          "type": "number",
          "description": "Seller's Stamp Duty paid, in SGD."
        },
        "cpfUsageInterest": {
          "type": "number",
          "description": "CPF refunded to the seller’s account, in SGD."
        }
      },
      "description": "The seller's side of the transfer.",
      "additionalProperties": false
    },
    "stages": {
      "type": "object",
      "required": [
        "cashDisbursed",
        "cpfDisbursed",
        "loanDisbursed",
        "loanMonthly"
      ],
      "properties": {
        "loanMonthly": {
          "type": "number",
          "description": "Monthly instalment on the total loan over maxTenureYears, in SGD."
        },
        "cpfDisbursed": {
          "type": "number",
          "description": "CPF paid towards the share, in SGD."
        },
        "cashDisbursed": {
          "type": "number",
          "description": "Cash payable beyond the deposit, in SGD."
        },
        "loanDisbursed": {
          "type": "number",
          "description": "Loan disbursed (equals maxLoanFromBank), in SGD."
        }
      },
      "description": "How the transfer is paid out.",
      "additionalProperties": false
    },
    "options": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "title",
          "description",
          "items",
          "total"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "label",
                "value"
              ],
              "properties": {
                "label": {
                  "type": "string",
                  "description": "Row label, e.g. \"Property Price\"."
                },
                "value": {
                  "type": "string",
                  "description": "Formatted value: an SGD amount or a citizenship code."
                }
              },
              "additionalProperties": false
            },
            "description": "Display rows for the option."
          },
          "title": {
            "type": "string",
            "description": "Option name, e.g. \"Option 1\"."
          },
          "total": {
            "type": "number",
            "description": "Total of stamp duties, legal fees and (when decoupling) SSD, in SGD."
          },
          "description": {
            "type": "string",
            "description": "What the option involves."
          }
        },
        "additionalProperties": false
      },
      "description": "Stamp-duty comparison for the next purchase when newPropertyPrice is given, else empty. Options 1-3 buy without decoupling (jointly, seller alone, buyer alone); options 4-5 decouple first, either way round."
    },
    "summary": {
      "type": "object",
      "required": [
        "sellerCashProceed",
        "buyerCashRequired"
      ],
      "properties": {
        "buyerCashRequired": {
          "type": "number",
          "description": "Cash the buyer must find: cash downpayment on the share, BSD, ABSD and legal fees, in SGD."
        },
        "sellerCashProceed": {
          "type": "number",
          "description": "Cash the seller walks away with, in SGD."
        }
      },
      "description": "Headline figures.",
      "additionalProperties": false
    },
    "initialPayment": {
      "type": "object",
      "required": [
        "depositPercent",
        "deposit",
        "bsd",
        "bsdLabel",
        "absd",
        "absdRate",
        "legalFees"
      ],
      "properties": {
        "bsd": {
          "type": "number",
          "description": "Buyer's Stamp Duty on the transferred share, in SGD."
        },
        "absd": {
          "type": "number",
          "description": "Additional Buyer’s Stamp Duty on the share, in SGD."
        },
        "deposit": {
          "type": "number",
          "description": "Minimum cash deposit, in SGD."
        },
        "absdRate": {
          "type": "number",
          "description": "ABSD rate applied, as a decimal fraction (0.2 = 20%)."
        },
        "bsdLabel": {
          "type": "string",
          "description": "Short label of the BSD formula applied."
        },
        "legalFees": {
          "type": "number",
          "description": "Buyer's conveyancing fees, in SGD."
        },
        "depositPercent": {
          "type": "number",
          "description": "Minimum cash deposit as a percentage of the share value (5 = 5%)."
        }
      },
      "description": "Upfront payments by the buyer.",
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}
analyze_decoupling annotations
{
  "title": "Decoupling analysis",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
calculate_sales_proceeds — Sales proceeds calculator
Advertised by the server during the last successful anonymous check.
Advertised read-only

Work out what a seller walks away with when selling a property: cash proceeds after redeeming the outstanding loan, refunding each owner’s CPF (principal plus accrued interest, 1 to 4 owners), and paying agent commission (with 9% GST), legal and miscellaneous fees. Flags a negative sale when the deductions exceed the selling price.

calculate_sales_proceeds input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "sellingPrice",
    "owners"
  ],
  "properties": {
    "owners": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "cpfUtilised": {
            "type": "number",
            "default": 0,
            "minimum": 0,
            "description": "CPF principal this owner used for the property (downpayment, instalments, stamp duty), in SGD."
          },
          "cpfAccruedInterest": {
            "type": "number",
            "default": 0,
            "minimum": 0,
            "description": "Accrued interest owed to this owner's CPF account on the CPF used, in SGD."
          }
        }
      },
      "maxItems": 4,
      "minItems": 1,
      "description": "CPF usage per owner (1 to 4 owners). Include every owner; use zeros for an owner who used no CPF."
    },
    "legalFees": {
      "type": "number",
      "default": 3000,
      "minimum": 0,
      "description": "Conveyancing fees for the sale, deducted from the proceeds, in SGD."
    },
    "sellingPrice": {
      "type": "number",
      "minimum": 0,
      "description": "Price the property sells for, in SGD; every deduction comes out of it."
    },
    "agentFeePercent": {
      "type": "number",
      "default": 1,
      "maximum": 5,
      "minimum": 0,
      "description": "Agent commission as a share of the selling price, as a percentage (e.g. 1.5 = 1.5%, not 0.015), before the 9% GST added on top."
    },
    "miscellaneousFees": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Other selling costs (repairs, valuation, admin), in SGD."
    },
    "outstandingLoanAmount": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Home loan still owed, repaid from the sale on completion, in SGD."
    }
  }
}
calculate_sales_proceeds output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "agentFees",
    "owners",
    "totalCpfRefund",
    "totalDeductions",
    "cashProceeds",
    "isNegativeSale"
  ],
  "properties": {
    "owners": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "cpfRefund"
        ],
        "properties": {
          "cpfRefund": {
            "type": "number",
            "description": "Refund to this owner's CPF account (principal utilised plus accrued interest), in SGD."
          }
        },
        "additionalProperties": false
      },
      "description": "Per-owner CPF refunds, in input order."
    },
    "agentFees": {
      "type": "number",
      "description": "Agent commission including 9% GST, in SGD."
    },
    "cashProceeds": {
      "type": "number",
      "description": "Cash left for the sellers after all deductions, in SGD. Negative means a negative sale: the sellers must top up cash to complete."
    },
    "isNegativeSale": {
      "type": "boolean",
      "description": "True when cash proceeds are negative."
    },
    "totalCpfRefund": {
      "type": "number",
      "description": "Total refunded to all owners' CPF accounts (not cash in hand), in SGD."
    },
    "perBankProceeds": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "bankSlug",
          "bankName",
          "cash",
          "cpf",
          "total",
          "monthlyInterest"
        ],
        "properties": {
          "cpf": {
            "type": "number",
            "description": "CPF headroom returned, in SGD."
          },
          "cash": {
            "type": "number",
            "description": "Cash headroom the bank allows from the sale, in SGD."
          },
          "total": {
            "type": "number",
            "description": "Total bridging headroom (cash + CPF), in SGD."
          },
          "bankName": {
            "type": "string",
            "description": "Bank display name."
          },
          "bankSlug": {
            "type": "string",
            "description": "Canonical bank id."
          },
          "monthlyInterest": {
            "type": "number",
            "description": "Interest-only monthly instalment on the total headroom, in SGD."
          }
        },
        "additionalProperties": false
      },
      "description": "Per-bank bridging headroom from the sale, when bank rules are supplied. Omitted otherwise."
    },
    "totalDeductions": {
      "type": "number",
      "description": "Everything deducted from the selling price: loan redemption, CPF refunds, agent fees, legal fees and miscellaneous costs, in SGD."
    }
  },
  "additionalProperties": false
}
calculate_sales_proceeds annotations
{
  "title": "Sales proceeds calculator",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
calculate_purchase_cost — Purchase cost & funding breakdown
Advertised by the server during the last successful anonymous check.
Advertised read-only

Calculate the full upfront cost of purchasing a Singapore residential property (HDB or private resale), with each cost line attributed to its funding source (cash, CPF or bank loan). Returns option fee, exercise fee, stamp duties (BSD and ABSD), downpayment breakdown, and a sufficiency check against available funds.

calculate_purchase_cost input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "propertyType",
    "purchasePrice"
  ],
  "properties": {
    "ltv": {
      "type": "number",
      "maximum": 0.9,
      "minimum": 0,
      "description": "Loan as a fraction of the purchase price (e.g. 0.75 = 75%). Used only when loanAmount is omitted."
    },
    "cpfFirst": {
      "type": "boolean",
      "description": "When true, fund CPF-eligible costs from a single CPF pool capped at availableCpfOA (CPF first, remainder cash), overriding the per-item cash/cpf funding sources. ABSD stays cash."
    },
    "buyerType": {
      "enum": [
        "singaporean",
        "pr",
        "foreigner",
        "entity",
        "trust"
      ],
      "type": "string",
      "default": "singaporean",
      "description": "Residency or legal status of the buyer; sets the ABSD rate."
    },
    "loanAmount": {
      "type": "number",
      "minimum": 0,
      "description": "Bank loan amount, in SGD. Takes precedence over `ltv`; with neither, a 75% loan is assumed."
    },
    "propertyType": {
      "enum": [
        "hdb",
        "private"
      ],
      "type": "string",
      "description": "Whether this is an HDB resale flat or a private property; sets the option and exercise fees, default legal and valuation fees, and stamp-duty funding."
    },
    "availableCash": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Cash the buyer can put towards the purchase, in SGD; checked in `cashSufficiency`."
    },
    "purchasePrice": {
      "type": "number",
      "minimum": 1,
      "description": "Agreed purchase price, in SGD; fees, stamp duties and downpayment follow from it."
    },
    "availableCpfOA": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "CPF Ordinary Account balance the buyer can put towards the purchase, in SGD; checked in `cpfSufficiency`."
    },
    "legalFeeFunding": {
      "enum": [
        "cash",
        "cpf"
      ],
      "type": "string",
      "default": "cash",
      "description": "Pay the legal/conveyancing fee from cash or from CPF."
    },
    "legalFeeOverride": {
      "type": "number",
      "minimum": 0,
      "description": "Legal/conveyancing fee to use instead of the standard estimate ($2,000 HDB, $3,000 private), in SGD."
    },
    "stampDutyFunding": {
      "enum": [
        "cash",
        "cpf"
      ],
      "type": "string",
      "description": "Pay the stamp duties (BSD and ABSD) from cash or from CPF. Omit for the usual convention: HDB pays BSD from CPF, private from cash, and ABSD is paid in cash."
    },
    "minimumCashPercent": {
      "type": "number",
      "default": 5,
      "description": "Minimum cash component as a percentage of price: 5 or 10 on a first housing loan, 25 with one already outstanding."
    },
    "valuationFeeOverride": {
      "type": "number",
      "minimum": 0,
      "description": "Override for valuation fee, in SGD. Defaults to $120 (HDB) or $350 (private)."
    },
    "existingPropertiesOwned": {
      "type": "integer",
      "default": 0,
      "maximum": 10,
      "minimum": 0,
      "description": "Residential properties the buyer already owns; sets the ABSD tier."
    }
  }
}
calculate_purchase_cost output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "costLines",
    "optionFee",
    "exerciseFee",
    "valuationFee",
    "legalFee",
    "bsd",
    "bsdBreakdown",
    "absd",
    "absdRate",
    "downpayment",
    "totalCost",
    "cashRequired",
    "cpfRequired",
    "cashSufficiency",
    "cpfSufficiency",
    "loanAmount",
    "ltv",
    "validation"
  ],
  "properties": {
    "bsd": {
      "type": "number",
      "description": "Buyer's Stamp Duty, in SGD."
    },
    "ltv": {
      "type": "number",
      "description": "Loan-to-value ratio as a decimal."
    },
    "absd": {
      "type": "number",
      "description": "Additional Buyer's Stamp Duty, in SGD."
    },
    "absdRate": {
      "type": "number",
      "description": "ABSD rate applied as a decimal (e.g. 0.2 = 20%)."
    },
    "legalFee": {
      "type": "number",
      "description": "Legal/conveyancing fee, in SGD."
    },
    "costLines": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "label",
          "amount",
          "fundingSource",
          "category"
        ],
        "properties": {
          "label": {
            "type": "string",
            "description": "Description of the cost item."
          },
          "amount": {
            "type": "number",
            "description": "Amount in SGD."
          },
          "category": {
            "enum": [
              "fee",
              "stamp_duty",
              "downpayment"
            ],
            "type": "string",
            "description": "Cost category."
          },
          "fundingSource": {
            "enum": [
              "cash",
              "cpf",
              "bank"
            ],
            "type": "string",
            "description": "Where this cost is funded from."
          }
        },
        "additionalProperties": false
      },
      "description": "Line-by-line breakdown of all costs."
    },
    "optionFee": {
      "type": "number",
      "description": "Option fee, in SGD."
    },
    "totalCost": {
      "type": "number",
      "description": "Total cost: price + all fees + stamp duties, in SGD."
    },
    "loanAmount": {
      "type": "number",
      "description": "Bank loan amount, in SGD."
    },
    "validation": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "check",
          "passed"
        ],
        "properties": {
          "check": {
            "type": "string",
            "description": "Name of the validation check."
          },
          "passed": {
            "type": "boolean",
            "description": "True if the check passed."
          },
          "message": {
            "type": "string",
            "description": "Explanation when the check fails."
          }
        },
        "additionalProperties": false
      },
      "description": "Self-check results."
    },
    "cpfRequired": {
      "type": "number",
      "description": "Total CPF required across all CPF-funded items, in SGD."
    },
    "downpayment": {
      "type": "object",
      "required": [
        "total",
        "minimumCash",
        "balanceMandatoryCash",
        "cpfDownpayment",
        "balanceInCash"
      ],
      "properties": {
        "total": {
          "type": "number",
          "description": "Total downpayment (price - loan), in SGD."
        },
        "minimumCash": {
          "type": "number",
          "description": "Minimum cash portion of price, in SGD."
        },
        "balanceInCash": {
          "type": "number",
          "description": "Remaining downpayment funded by additional cash, in SGD."
        },
        "cpfDownpayment": {
          "type": "number",
          "description": "CPF portion of downpayment, in SGD."
        },
        "balanceMandatoryCash": {
          "type": "number",
          "description": "Balance of mandatory cash after option + exercise fees, in SGD."
        }
      },
      "description": "Downpayment split.",
      "additionalProperties": false
    },
    "exerciseFee": {
      "type": "number",
      "description": "Exercise fee, in SGD."
    },
    "bsdBreakdown": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "bracket",
          "amount"
        ],
        "properties": {
          "amount": {
            "type": "number"
          },
          "bracket": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "description": "BSD per bracket."
    },
    "cashRequired": {
      "type": "number",
      "description": "Total cash required across all cash-funded items, in SGD."
    },
    "valuationFee": {
      "type": "number",
      "description": "Valuation fee, in SGD."
    },
    "cpfSufficiency": {
      "type": "object",
      "required": [
        "required",
        "available",
        "surplusOrShortfall",
        "isSufficient"
      ],
      "properties": {
        "required": {
          "type": "number",
          "description": "Amount required, in SGD."
        },
        "available": {
          "type": "number",
          "description": "Amount available, in SGD."
        },
        "isSufficient": {
          "type": "boolean",
          "description": "True if available >= required."
        },
        "surplusOrShortfall": {
          "type": "number",
          "description": "Positive = surplus, negative = shortfall."
        }
      },
      "description": "CPF surplus or shortfall.",
      "additionalProperties": false
    },
    "cashSufficiency": {
      "type": "object",
      "required": [
        "required",
        "available",
        "surplusOrShortfall",
        "isSufficient"
      ],
      "properties": {
        "required": {
          "type": "number",
          "description": "Amount required, in SGD."
        },
        "available": {
          "type": "number",
          "description": "Amount available, in SGD."
        },
        "isSufficient": {
          "type": "boolean",
          "description": "True if available >= required."
        },
        "surplusOrShortfall": {
          "type": "number",
          "description": "Positive = surplus, negative = shortfall."
        }
      },
      "description": "Cash surplus or shortfall.",
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}
calculate_purchase_cost annotations
{
  "title": "Purchase cost & funding breakdown",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
calculate_hdb_resale_timeline — HDB resale purchase timeline
Advertised by the server during the last successful anonymous check.
Advertised read-only

Generate a step-by-step dated timeline for an HDB resale purchase, from securing the Option to Purchase through to completion and key collection. Returns the ordered steps (what happens, the date, any payment due and its CPF/cash split), a summary (total payments, CPF and cash totals, completion date, duration in days), BSD and ABSD, the funding split (LTV, downpayment, minimum cash, cash-or-CPF remainder, monthly instalment when a rate and tenure are given), the completion date used, and self-checks.

calculate_hdb_resale_timeline input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "purchasePrice",
    "optionDate",
    "optionExpiryDate",
    "exerciseDate"
  ],
  "properties": {
    "cpfUsage": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "CPF OA savings the buyer will put towards the purchase, in SGD. Drawn down in step order against the legal fee, stamp duty and completion payment, the only payments CPF may make here; each payment reports its `cpf` and `cash` parts."
    },
    "legalFee": {
      "type": "number",
      "default": 2000,
      "minimum": 0,
      "description": "Conveyancing fee for the purchase, in SGD; part of the total outlay."
    },
    "buyerType": {
      "enum": [
        "singaporean",
        "pr",
        "foreigner",
        "entity",
        "trust"
      ],
      "type": "string",
      "default": "singaporean",
      "description": "Residency or legal status of the buyer; sets the ABSD rate."
    },
    "optionFee": {
      "type": "number",
      "default": 1000,
      "minimum": 0,
      "description": "Fee paid to the seller when the Option to Purchase is granted, in SGD; HDB caps it at $1,000."
    },
    "otherFees": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Other costs (valuation, repairs, admin), in SGD. Reported alongside legal fees and stamp duty as part of the total outlay."
    },
    "loanAmount": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Housing loan amount, in SGD; 0 for a cash purchase. Sets the LTV band, the minimum cash and the completion payment."
    },
    "optionDate": {
      "type": "string",
      "description": "Date the Option to Purchase is granted, e.g. 2026-08-01; the timeline starts here."
    },
    "exerciseFee": {
      "type": "number",
      "default": 4000,
      "minimum": 0,
      "description": "Fee paid to the seller when the option is exercised, in SGD."
    },
    "tenureYears": {
      "type": "integer",
      "maximum": 35,
      "minimum": 1,
      "description": "Loan tenure in years. Supply with interestRate to get `funding.monthlyInstalment`."
    },
    "exerciseDate": {
      "type": "string",
      "description": "Date the option is exercised (ISO 8601)."
    },
    "interestRate": {
      "type": "number",
      "maximum": 15,
      "minimum": 0.1,
      "description": "Annual loan interest rate, as a percentage (e.g. 3.5 = 3.5%). Supply with tenureYears to get `funding.monthlyInstalment`; otherwise it is null."
    },
    "purchasePrice": {
      "type": "number",
      "minimum": 1,
      "description": "Agreed resale price of the HDB flat, in SGD; sets the stamp duties and downpayment."
    },
    "completionDate": {
      "type": "string",
      "description": "Completion date (ISO 8601). Defaults to exerciseDate + 70 calendar days if not provided."
    },
    "optionExpiryDate": {
      "type": "string",
      "description": "Last day to exercise the option (ISO 8601)."
    },
    "existingPropertiesOwned": {
      "type": "integer",
      "default": 0,
      "maximum": 10,
      "minimum": 0,
      "description": "Residential properties the buyer already owns; sets the ABSD tier."
    }
  }
}
calculate_hdb_resale_timeline output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "steps",
    "summary",
    "stampDuty",
    "otherFees",
    "funding",
    "completionDateUsed",
    "completionDateIsDefault",
    "validation"
  ],
  "properties": {
    "steps": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "stepNumber",
          "description",
          "date",
          "dayOffset",
          "anchor",
          "payment"
        ],
        "properties": {
          "date": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Computed date (ISO 8601), or null for sequence-only steps."
          },
          "anchor": {
            "enum": [
              "option",
              "expiry",
              "exercise",
              "none"
            ],
            "type": "string",
            "description": "Which anchor date this step is relative to."
          },
          "payment": {
            "anyOf": [
              {
                "type": "object",
                "required": [
                  "amount",
                  "label",
                  "cpf",
                  "cash"
                ],
                "properties": {
                  "cpf": {
                    "type": "number",
                    "description": "Part paid from the CPF given as cpfUsage, in SGD. Only the legal fee and completion payment (and, for HDB resale, stamp duty) may use CPF."
                  },
                  "cash": {
                    "type": "number",
                    "description": "Part paid in cash, in SGD (amount less cpf)."
                  },
                  "label": {
                    "type": "string",
                    "description": "Payment description."
                  },
                  "amount": {
                    "type": "number",
                    "description": "Payment due at this step, in SGD."
                  }
                },
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ],
            "description": "Payment due at this step, or null if no payment."
          },
          "dayOffset": {
            "anyOf": [
              {
                "type": "integer",
                "maximum": 9007199254740991,
                "minimum": -9007199254740991
              },
              {
                "type": "null"
              }
            ],
            "description": "Day offset from the anchor date, or null."
          },
          "stepNumber": {
            "type": "integer",
            "maximum": 9007199254740991,
            "minimum": -9007199254740991,
            "description": "Step sequence number."
          },
          "description": {
            "type": "string",
            "description": "What happens at this step."
          }
        },
        "additionalProperties": false
      },
      "description": "Ordered list of timeline steps."
    },
    "funding": {
      "type": "object",
      "required": [
        "ltv",
        "downpaymentTotal",
        "downpaymentPercent",
        "minimumCashPercent",
        "minimumCash",
        "cashOrCpf",
        "monthlyInstalment"
      ],
      "properties": {
        "ltv": {
          "type": "number",
          "description": "Loan as a fraction of the purchase price (0.75 = 75%)."
        },
        "cashOrCpf": {
          "type": "number",
          "description": "Remainder of the downpayment, payable in cash or CPF OA, in SGD."
        },
        "minimumCash": {
          "type": "number",
          "description": "Minimum cash portion of the downpayment, in SGD."
        },
        "downpaymentTotal": {
          "type": "number",
          "description": "Purchase price less the loan, in SGD."
        },
        "monthlyInstalment": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "description": "Monthly instalment, in SGD, or null when no rate and tenure were supplied."
        },
        "downpaymentPercent": {
          "type": "number",
          "description": "Downpayment as a fraction of the purchase price (0.25 = 25%)."
        },
        "minimumCashPercent": {
          "type": "number",
          "description": "Minimum cash portion required, as a fraction of the price (0.05 = 5%)."
        }
      },
      "description": "Loan, downpayment split and instalment.",
      "additionalProperties": false
    },
    "summary": {
      "type": "object",
      "required": [
        "totalPayments",
        "totalCpf",
        "totalCash",
        "completionDate",
        "durationDays"
      ],
      "properties": {
        "totalCpf": {
          "type": "number",
          "description": "Part of totalPayments paid from CPF, in SGD."
        },
        "totalCash": {
          "type": "number",
          "description": "Part of totalPayments paid in cash, in SGD."
        },
        "durationDays": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "Elapsed days from option date to completion."
        },
        "totalPayments": {
          "type": "number",
          "description": "Sum of all payments across the timeline, in SGD."
        },
        "completionDate": {
          "type": "string",
          "description": "Date of the final dated step (ISO 8601)."
        }
      },
      "description": "Timeline summary.",
      "additionalProperties": false
    },
    "otherFees": {
      "type": "number",
      "description": "Other costs supplied by the caller, in SGD."
    },
    "stampDuty": {
      "type": "object",
      "required": [
        "bsd",
        "absd"
      ],
      "properties": {
        "bsd": {
          "type": "number",
          "description": "Buyer's Stamp Duty, in SGD."
        },
        "absd": {
          "type": "number",
          "description": "Additional Buyer's Stamp Duty, in SGD."
        }
      },
      "additionalProperties": false
    },
    "validation": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "check",
          "passed"
        ],
        "properties": {
          "check": {
            "type": "string",
            "description": "Name of the validation check."
          },
          "passed": {
            "type": "boolean",
            "description": "True if the check passed."
          },
          "message": {
            "type": "string",
            "description": "Explanation when the check fails."
          }
        },
        "additionalProperties": false
      },
      "description": "Self-check results."
    },
    "completionDateUsed": {
      "type": "string",
      "description": "The completion date used (entered or defaulted)."
    },
    "completionDateIsDefault": {
      "type": "boolean",
      "description": "True if the completion date was defaulted (not entered)."
    }
  },
  "additionalProperties": false
}
calculate_hdb_resale_timeline annotations
{
  "title": "HDB resale purchase timeline",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
calculate_private_resale_timeline — Private resale purchase timeline
Advertised by the server during the last successful anonymous check.
Advertised read-only

Generate a step-by-step dated timeline for a private (non-HDB) resale purchase, with the 1% option fee, 4% exercise payment, stamp duties and completion payment at the right steps. Exercise date defaults to 14 calendar days inclusive of the option date if not entered.

calculate_private_resale_timeline input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "purchasePrice",
    "optionDate",
    "optionExpiryDate"
  ],
  "properties": {
    "cpfUsage": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "CPF OA savings the buyer will put towards the purchase, in SGD. Drawn down in step order against the legal fee and completion payment, the only payments CPF may make; each payment reports its `cpf` and `cash` parts."
    },
    "legalFee": {
      "type": "number",
      "default": 2500,
      "minimum": 0,
      "description": "Conveyancing fee for the purchase, in SGD; part of the total outlay."
    },
    "buyerType": {
      "enum": [
        "singaporean",
        "pr",
        "foreigner",
        "entity",
        "trust"
      ],
      "type": "string",
      "default": "singaporean",
      "description": "Residency or legal status of the buyer; sets the ABSD rate."
    },
    "otherFees": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Other costs (valuation, repairs, admin), in SGD. Reported alongside legal fees and stamp duty as part of the total outlay."
    },
    "loanAmount": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Housing loan amount, in SGD; 0 for a cash purchase. Sets the LTV band, the minimum cash and the completion payment."
    },
    "optionDate": {
      "type": "string",
      "description": "Option to Purchase date (ISO 8601)."
    },
    "tenureYears": {
      "type": "integer",
      "maximum": 35,
      "minimum": 1,
      "description": "Loan tenure in years. Supply with interestRate to get `funding.monthlyInstalment`."
    },
    "exerciseDate": {
      "type": "string",
      "description": "Exercise date (ISO 8601). Defaults to optionDate + 13 calendar days if not provided."
    },
    "interestRate": {
      "type": "number",
      "maximum": 15,
      "minimum": 0.1,
      "description": "Annual loan interest rate, as a percentage (e.g. 3.5 = 3.5%). Supply with tenureYears to get `funding.monthlyInstalment`; otherwise it is null."
    },
    "purchasePrice": {
      "type": "number",
      "minimum": 1,
      "description": "Agreed purchase price of the private property, in SGD; sets the option fee, exercise payment, stamp duties and downpayment."
    },
    "completionDate": {
      "type": "string",
      "description": "Completion date (ISO 8601). Defaults to exerciseDate + 70 calendar days if not provided."
    },
    "optionExpiryDate": {
      "type": "string",
      "description": "Last day to exercise the option (ISO 8601)."
    },
    "existingPropertiesOwned": {
      "type": "integer",
      "default": 0,
      "maximum": 10,
      "minimum": 0,
      "description": "Residential properties the buyer already owns; sets the ABSD tier."
    }
  }
}
calculate_private_resale_timeline output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "steps",
    "summary",
    "stampDuty",
    "otherFees",
    "funding",
    "exerciseDateUsed",
    "exerciseDateIsDefault",
    "completionDateUsed",
    "completionDateIsDefault",
    "validation"
  ],
  "properties": {
    "steps": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "stepNumber",
          "description",
          "date",
          "dayOffset",
          "anchor",
          "payment"
        ],
        "properties": {
          "date": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Computed date (ISO 8601), or null for sequence-only steps."
          },
          "anchor": {
            "enum": [
              "option",
              "expiry",
              "exercise",
              "none"
            ],
            "type": "string",
            "description": "Which anchor date this step is relative to."
          },
          "payment": {
            "anyOf": [
              {
                "type": "object",
                "required": [
                  "amount",
                  "label",
                  "cpf",
                  "cash"
                ],
                "properties": {
                  "cpf": {
                    "type": "number",
                    "description": "Part paid from the CPF given as cpfUsage, in SGD. Only the legal fee and completion payment (and, for HDB resale, stamp duty) may use CPF."
                  },
                  "cash": {
                    "type": "number",
                    "description": "Part paid in cash, in SGD (amount less cpf)."
                  },
                  "label": {
                    "type": "string",
                    "description": "Payment description."
                  },
                  "amount": {
                    "type": "number",
                    "description": "Payment due at this step, in SGD."
                  }
                },
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ],
            "description": "Payment due at this step, or null if no payment."
          },
          "dayOffset": {
            "anyOf": [
              {
                "type": "integer",
                "maximum": 9007199254740991,
                "minimum": -9007199254740991
              },
              {
                "type": "null"
              }
            ],
            "description": "Day offset from the anchor date, or null."
          },
          "stepNumber": {
            "type": "integer",
            "maximum": 9007199254740991,
            "minimum": -9007199254740991,
            "description": "Step sequence number."
          },
          "description": {
            "type": "string",
            "description": "What happens at this step."
          }
        },
        "additionalProperties": false
      },
      "description": "Ordered list of timeline steps."
    },
    "funding": {
      "type": "object",
      "required": [
        "ltv",
        "downpaymentTotal",
        "downpaymentPercent",
        "minimumCashPercent",
        "minimumCash",
        "cashOrCpf",
        "monthlyInstalment"
      ],
      "properties": {
        "ltv": {
          "type": "number",
          "description": "Loan as a fraction of the purchase price (0.75 = 75%)."
        },
        "cashOrCpf": {
          "type": "number",
          "description": "Remainder of the downpayment, payable in cash or CPF OA, in SGD."
        },
        "minimumCash": {
          "type": "number",
          "description": "Minimum cash portion of the downpayment, in SGD."
        },
        "downpaymentTotal": {
          "type": "number",
          "description": "Purchase price less the loan, in SGD."
        },
        "monthlyInstalment": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "description": "Monthly instalment, in SGD, or null when no rate and tenure were supplied."
        },
        "downpaymentPercent": {
          "type": "number",
          "description": "Downpayment as a fraction of the purchase price (0.25 = 25%)."
        },
        "minimumCashPercent": {
          "type": "number",
          "description": "Minimum cash portion required, as a fraction of the price (0.05 = 5%)."
        }
      },
      "description": "Loan, downpayment split and instalment.",
      "additionalProperties": false
    },
    "summary": {
      "type": "object",
      "required": [
        "totalPayments",
        "totalCpf",
        "totalCash",
        "completionDate",
        "durationDays"
      ],
      "properties": {
        "totalCpf": {
          "type": "number",
          "description": "Part of totalPayments paid from CPF, in SGD."
        },
        "totalCash": {
          "type": "number",
          "description": "Part of totalPayments paid in cash, in SGD."
        },
        "durationDays": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "Elapsed days from option date to completion."
        },
        "totalPayments": {
          "type": "number",
          "description": "Sum of all payments across the timeline, in SGD."
        },
        "completionDate": {
          "type": "string",
          "description": "Date of the final dated step (ISO 8601)."
        }
      },
      "description": "Timeline summary.",
      "additionalProperties": false
    },
    "otherFees": {
      "type": "number",
      "description": "Other costs supplied by the caller, in SGD."
    },
    "stampDuty": {
      "type": "object",
      "required": [
        "bsd",
        "absd"
      ],
      "properties": {
        "bsd": {
          "type": "number",
          "description": "Buyer's Stamp Duty, in SGD."
        },
        "absd": {
          "type": "number",
          "description": "Additional Buyer's Stamp Duty, in SGD."
        }
      },
      "additionalProperties": false
    },
    "validation": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "check",
          "passed"
        ],
        "properties": {
          "check": {
            "type": "string",
            "description": "Name of the validation check."
          },
          "passed": {
            "type": "boolean",
            "description": "True if the check passed."
          },
          "message": {
            "type": "string",
            "description": "Explanation when the check fails."
          }
        },
        "additionalProperties": false
      },
      "description": "Self-check results."
    },
    "exerciseDateUsed": {
      "type": "string",
      "description": "The exercise date used (entered or defaulted)."
    },
    "completionDateUsed": {
      "type": "string",
      "description": "The completion date used (entered or defaulted)."
    },
    "exerciseDateIsDefault": {
      "type": "boolean",
      "description": "True if the exercise date was defaulted (not entered)."
    },
    "completionDateIsDefault": {
      "type": "boolean",
      "description": "True if the completion date was defaulted (not entered)."
    }
  },
  "additionalProperties": false
}
calculate_private_resale_timeline annotations
{
  "title": "Private resale purchase timeline",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
calculate_progressive_payment — Progressive payment (BUC) calculator
Advertised by the server during the last successful anonymous check.
Advertised read-only

Model the monthly instalment profile of a Singapore Building Under Construction (BUC) property loan across progressive payment stages. Handles fixed and floating rate packages, per-stage cash/CPF/loan funding split, drawdown timing, rate stepping by year, and optional prepayments. Set includeSchedule to true for the full month-by-month amortisation schedule (summary is always included).

calculate_progressive_payment input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "purchasePrice",
    "tenureYears",
    "otpDate",
    "topDate",
    "ratePerYear"
  ],
  "properties": {
    "stages": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "name",
          "percent"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Stage name (e.g. \"Foundation work\")."
          },
          "percent": {
            "type": "number",
            "maximum": 100,
            "minimum": 0,
            "description": "Share of the purchase price payable at this stage, in percent."
          }
        }
      },
      "description": "Custom stage schedule. Defaults to the standard 10-stage BUC schedule. Percentages must sum to 100."
    },
    "otpDate": {
      "type": "string",
      "description": "Option to Purchase date (ISO 8601)."
    },
    "topDate": {
      "type": "string",
      "description": "Expected Temporary Occupation Permit date (ISO 8601)."
    },
    "rateMode": {
      "enum": [
        "fixed",
        "floating"
      ],
      "type": "string",
      "default": "fixed",
      "description": "How each year's rate is set: 'fixed' takes it from ratePerYear; 'floating' uses referenceRate plus that year's spreadPerYear and ignores ratePerYear."
    },
    "loanAmount": {
      "type": "number",
      "minimum": 0,
      "description": "Loan amount in SGD. Takes precedence over loanQuantum."
    },
    "loanQuantum": {
      "type": "number",
      "maximum": 0.9,
      "minimum": 0,
      "description": "Loan as a fraction of the price (e.g. 0.75 = 75%). Used only when loanAmount is omitted; with neither, 75% is assumed."
    },
    "prepayments": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "month",
          "amount"
        ],
        "properties": {
          "month": {
            "type": "integer",
            "maximum": 9007199254740991,
            "minimum": 1,
            "description": "Loan month of the prepayment, counting from 1 at the first drawdown (as in `schedule[].month`)."
          },
          "amount": {
            "type": "number",
            "minimum": 0,
            "description": "Prepayment amount, in SGD."
          }
        }
      },
      "default": [],
      "description": "Optional list of prepayments."
    },
    "ratePerYear": {
      "type": "array",
      "items": {
        "type": "number",
        "minimum": 0
      },
      "minItems": 1,
      "description": "Annual interest rates by year as percentages (e.g. [2.5, 2.5, 3.0, 3.5, 3.5, 3.5] for years 1-5 and thereafter). Last value applies to all subsequent years."
    },
    "tenureYears": {
      "type": "integer",
      "maximum": 35,
      "minimum": 1,
      "description": "Loan tenure in years, counted from the first drawdown."
    },
    "lockInMonths": {
      "type": "integer",
      "default": 0,
      "maximum": 9007199254740991,
      "minimum": 0,
      "description": "Lock-in period in months from first drawdown."
    },
    "purchasePrice": {
      "type": "number",
      "minimum": 1,
      "description": "Purchase price of the BUC property, in SGD; each stage's payment is a share of it."
    },
    "referenceRate": {
      "type": "number",
      "minimum": 0,
      "description": "Floating mode only: the benchmark rate such as 3M SORA, as a percentage (e.g. 3.5 = 3.5%), to which each year's spread is added. Omitted, it is taken as 0, so the rate is the spread alone. Ignored in fixed mode."
    },
    "spreadPerYear": {
      "type": "array",
      "items": {
        "type": "number"
      },
      "description": "Spread per year for floating mode, as percentages. Last value applies to all subsequent years."
    },
    "availableCpfOA": {
      "type": "number",
      "minimum": 0,
      "description": "Available CPF Ordinary Account balance, in SGD. When set, each stage’s equity CPF portion is capped at the remaining OA balance and the rest is funded by cash. When omitted, CPF covers all equity above the minimum-cash floor."
    },
    "cscOffsetMonths": {
      "type": "integer",
      "default": 12,
      "maximum": 9007199254740991,
      "minimum": 0,
      "description": "Months from TOP to CSC (default 12)."
    },
    "includeSchedule": {
      "type": "boolean",
      "default": false,
      "description": "If true, include the full month-by-month amortisation schedule in output."
    },
    "prepaymentOutcome": {
      "enum": [
        "reduceInstalment",
        "shortenTenure"
      ],
      "type": "string",
      "default": "reduceInstalment",
      "description": "What happens after a prepayment: lower instalment or shorter tenure."
    },
    "minimumCashPercent": {
      "type": "number",
      "default": 5,
      "maximum": 100,
      "minimum": 0,
      "description": "Minimum cash downpayment as a percentage of the purchase price (e.g. 5 = 5%); CPF or cash covers the rest of the equity."
    },
    "prepaymentPenaltyPercent": {
      "type": "number",
      "default": 0,
      "minimum": 0,
      "description": "Penalty as a percentage of prepaid amount if within lock-in period."
    }
  }
}
calculate_progressive_payment output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "derived",
    "stages",
    "summary",
    "cumulativeInterestByYear",
    "prepaymentSummary",
    "validation"
  ],
  "properties": {
    "stages": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "name",
          "percent",
          "amount",
          "cashPortion",
          "cpfPortion",
          "loanPortion",
          "drawdownMonth",
          "instalmentAfterStage"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Stage name."
          },
          "amount": {
            "type": "number",
            "description": "Dollar amount for this stage, in SGD."
          },
          "percent": {
            "type": "number",
            "description": "Percentage of price."
          },
          "cpfPortion": {
            "type": "number",
            "description": "CPF-funded portion, in SGD."
          },
          "cashPortion": {
            "type": "number",
            "description": "Cash-funded portion, in SGD."
          },
          "loanPortion": {
            "type": "number",
            "description": "Loan-funded portion (drawdown), in SGD."
          },
          "drawdownMonth": {
            "anyOf": [
              {
                "type": "integer",
                "maximum": 9007199254740991,
                "minimum": -9007199254740991
              },
              {
                "type": "null"
              }
            ],
            "description": "Month in the amortisation schedule when drawdown occurs, or null if fully equity-funded."
          },
          "instalmentAfterStage": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "Monthly instalment after this drawdown, or null if no drawdown."
          }
        },
        "additionalProperties": false
      },
      "description": "Per-stage funding breakdown."
    },
    "derived": {
      "type": "object",
      "required": [
        "loanAmount",
        "ltv",
        "equity",
        "minimumCash",
        "cashOrCpf",
        "constructionMonths"
      ],
      "properties": {
        "ltv": {
          "type": "number",
          "description": "Loan-to-value ratio as a decimal."
        },
        "equity": {
          "type": "number",
          "description": "Equity portion (price - loan), in SGD."
        },
        "cashOrCpf": {
          "type": "number",
          "description": "Equity remainder payable by cash or CPF, in SGD."
        },
        "loanAmount": {
          "type": "number",
          "description": "Loan amount, in SGD."
        },
        "minimumCash": {
          "type": "number",
          "description": "Minimum cash requirement, in SGD."
        },
        "constructionMonths": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "Months from OTP to TOP."
        }
      },
      "additionalProperties": false
    },
    "summary": {
      "type": "object",
      "required": [
        "firstInstalment",
        "finalInstalment",
        "maxInstalment",
        "totalInterest",
        "totalPaid",
        "payoffMonth"
      ],
      "properties": {
        "totalPaid": {
          "type": "number",
          "description": "Total of all instalments + prepayments, in SGD."
        },
        "payoffMonth": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "Month in which the loan is fully repaid."
        },
        "maxInstalment": {
          "type": "number",
          "description": "Highest monthly instalment during the loan, in SGD."
        },
        "totalInterest": {
          "type": "number",
          "description": "Total interest over the life of the loan, in SGD."
        },
        "finalInstalment": {
          "type": "number",
          "description": "Final monthly instalment, in SGD."
        },
        "firstInstalment": {
          "type": "number",
          "description": "First monthly instalment (after first drawdown), in SGD."
        }
      },
      "additionalProperties": false
    },
    "schedule": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "month",
          "openingBalance",
          "drawdown",
          "annualRate",
          "instalment",
          "interest",
          "principal",
          "prepayment",
          "endingBalance"
        ],
        "properties": {
          "month": {
            "type": "integer",
            "maximum": 9007199254740991,
            "minimum": -9007199254740991,
            "description": "Month index (1-based)."
          },
          "drawdown": {
            "type": "number",
            "description": "Drawdown this month, in SGD."
          },
          "interest": {
            "type": "number",
            "description": "Interest portion, in SGD."
          },
          "principal": {
            "type": "number",
            "description": "Principal portion, in SGD."
          },
          "annualRate": {
            "type": "number",
            "description": "Applicable annual rate as a percentage."
          },
          "instalment": {
            "type": "number",
            "description": "Monthly instalment, in SGD."
          },
          "prepayment": {
            "type": "number",
            "description": "Prepayment applied this month, in SGD."
          },
          "endingBalance": {
            "type": "number",
            "description": "Ending balance, in SGD."
          },
          "openingBalance": {
            "type": "number",
            "description": "Opening disbursed balance, in SGD."
          }
        },
        "additionalProperties": false
      },
      "description": "Full month-by-month schedule (only when includeSchedule is true)."
    },
    "validation": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "check",
          "passed"
        ],
        "properties": {
          "check": {
            "type": "string",
            "description": "Name of the validation check."
          },
          "passed": {
            "type": "boolean",
            "description": "True if the check passed."
          },
          "message": {
            "type": "string",
            "description": "Explanation when the check fails."
          }
        },
        "additionalProperties": false
      },
      "description": "Self-check results."
    },
    "prepaymentSummary": {
      "anyOf": [
        {
          "type": "object",
          "required": [
            "interestSaved",
            "monthsSaved",
            "lockInPenalty"
          ],
          "properties": {
            "monthsSaved": {
              "type": "integer",
              "maximum": 9007199254740991,
              "minimum": -9007199254740991,
              "description": "Months saved (shorten-tenure mode) or 0."
            },
            "interestSaved": {
              "type": "number",
              "description": "Total interest saved vs no-prepayment baseline, in SGD."
            },
            "lockInPenalty": {
              "type": "number",
              "description": "Lock-in penalty incurred, in SGD."
            }
          },
          "additionalProperties": false
        },
        {
          "type": "null"
        }
      ],
      "description": "Prepayment impact summary, or null if no prepayments."
    },
    "cumulativeInterestByYear": {
      "type": "array",
      "items": {
        "type": "number"
      },
      "description": "Cumulative interest at the end of each year, in SGD."
    }
  },
  "additionalProperties": false
}
calculate_progressive_payment annotations
{
  "title": "Progressive payment (BUC) calculator",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}

Prompts

Not advertised

No prompt definitions available.

Resources

Not advertised

No resource definitions available.

30-day availability

Aggregated from read-only remote checks; this is not a tool-execution or security guarantee.

Successful checks
1 of 1 (100%)
Average latency
2112 ms
95th percentile latency
2112 ms

Maintenance history

Public check and source-refresh events. Editorial and ownership records remain private.

  • · probe · passed · 2112 ms · metadata changed · 23 tool(s)

Connect from your client

Each snippet registers the same endpoint; pick the client you use.

This server is operated by the named third party. Listing and endpoint checks are not an endorsement or a security audit. Review the operator's permissions, terms, and data handling before connecting. How listings are checked and corrected.

Claude Code

Run this once in a terminal. It registers the remote server. Use --scope project to write .mcp.json for the repo.
Claude Code configuration
claude mcp add --transport http cashew-mcp https://www.cashew.sg/mcp

Codex

Run this once. Codex stores MCP servers in ~/.codex/config.toml (or .codex/config.toml in a trusted project).
Codex configuration
codex mcp add cashew-mcp --url https://www.cashew.sg/mcp

Cursor

Merge this server into .cursor/mcp.json in your project, then enable cashew-mcp in your MCP settings.
Cursor configuration
{
  "mcpServers": {
    "cashew-mcp": {
      "url": "https://www.cashew.sg/mcp"
    }
  }
}

Cline

Merge this into cline_mcp_settings.json (Cline → MCP Servers → Configure).
Cline configuration
{
  "mcpServers": {
    "cashew-mcp": {
      "type": "streamableHttp",
      "url": "https://www.cashew.sg/mcp",
      "disabled": false
    }
  }
}

VS Code

Merge this server into .vscode/mcp.json. Use MCP: List Servers to start cashew-mcp and inspect its tools.
VS Code configuration
{
  "servers": {
    "cashew-mcp": {
      "type": "http",
      "url": "https://www.cashew.sg/mcp"
    }
  }
}

Gemini CLI

Run this once. It writes .gemini/settings.json in the current project; pass --scope user for ~/.gemini/settings.json.
Gemini CLI configuration
gemini mcp add --transport http cashew-mcp https://www.cashew.sg/mcp

Claude Desktop

Merge this into claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/, Windows: %APPDATA%\Claude\), then restart Claude Desktop.
Claude Desktop configuration
{
  "mcpServers": {
    "cashew-mcp": {
      "url": "https://www.cashew.sg/mcp"
    }
  }
}

Windsurf

Merge this into ~/.codeium/windsurf/mcp_config.json, then refresh MCP in Cascade. Windsurf remote servers use serverUrl, not url.
Windsurf configuration
{
  "mcpServers": {
    "cashew-mcp": {
      "serverUrl": "https://www.cashew.sg/mcp"
    }
  }
}

Let your agent find its tools

Find servers like Cashew MCP from your agent. Add Koskamo once to search the index for endpoints, access requirements, and client setup. Public discovery needs no key.

Do you run Cashew MCP?

Prove ownership with a DNS record, a well-known file, or your Official MCP Registry entry, and your edits publish immediately. Proofs are re-checked weekly; admins can still hide or remove a listing.

Claim this listing