{
  "openapi": "3.1.0",
  "info": {
    "title": "KeptDo Underwriting API",
    "version": "1.0.0",
    "summary": "Multifamily underwriting math as a public, no-auth JSON API.",
    "description": "The same calculations that power the free tools at https://keptdo.com/free-tools, computed from the same source module. Every endpoint is a pure function of its query parameters: no authentication, no rate-limit tiers, no state, no side effects. Results are screens, not investment advice.",
    "contact": {
      "name": "KeptDo",
      "url": "https://keptdo.com",
      "email": "mdelval@delvalinvestmentgroup.com"
    },
    "license": {
      "name": "Terms of Service",
      "url": "https://keptdo.com/terms"
    }
  },
  "servers": [{ "url": "https://keptdo.com/api/v1", "description": "Production" }],
  "externalDocs": {
    "description": "Human-readable API documentation",
    "url": "https://keptdo.com/docs/api"
  },
  "tags": [
    { "name": "underwriting", "description": "Deal-level underwriting math." },
    { "name": "debt", "description": "Loan payment and amortization." },
    { "name": "returns", "description": "Return metrics." },
    { "name": "service", "description": "Service metadata." }
  ],
  "paths": {
    "/health": {
      "get": {
        "operationId": "getHealth",
        "tags": ["service"],
        "summary": "Service liveness",
        "description": "Returns service status and pointers to documentation. Referenced by the `status` relation in /.well-known/api-catalog.",
        "responses": {
          "200": {
            "description": "Service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": { "type": "string", "const": "ok" },
                    "service": { "type": "string" },
                    "version": { "type": "string" },
                    "documentation": { "type": "string", "format": "uri" },
                    "specification": { "type": "string", "format": "uri" }
                  },
                  "required": ["status", "service", "version"]
                }
              }
            }
          }
        }
      }
    },
    "/noi": {
      "get": {
        "operationId": "calculateNoi",
        "tags": ["underwriting"],
        "summary": "Net operating income from cap rate and price",
        "description": "NOI = cap rate x purchase price.",
        "parameters": [
          {
            "name": "capRate",
            "in": "query",
            "required": true,
            "description": "Capitalization rate as a percent, e.g. 6.5 for 6.5%.",
            "schema": { "type": "number", "minimum": 0.01, "maximum": 25 },
            "example": 6.5
          },
          {
            "name": "price",
            "in": "query",
            "required": true,
            "description": "Purchase price in dollars.",
            "schema": { "type": "number", "minimum": 0 },
            "example": 8000000
          }
        ],
        "responses": {
          "200": {
            "description": "Calculated NOI.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "inputs": { "type": "object" },
                    "result": {
                      "type": "object",
                      "properties": {
                        "noi": { "type": ["number", "null"] },
                        "noiMonthly": { "type": ["number", "null"] }
                      }
                    },
                    "formula": { "type": "string" },
                    "disclaimer": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/cap-rate": {
      "get": {
        "operationId": "calculateCapRate",
        "tags": ["underwriting"],
        "summary": "Cap rate from NOI and price",
        "description": "Cap rate = NOI / purchase price. Optionally returns price per unit.",
        "parameters": [
          {
            "name": "noi",
            "in": "query",
            "required": true,
            "description": "Annual net operating income in dollars.",
            "schema": { "type": "number", "minimum": 0 },
            "example": 570000
          },
          {
            "name": "price",
            "in": "query",
            "required": true,
            "description": "Purchase price in dollars.",
            "schema": { "type": "number", "minimum": 0 },
            "example": 8000000
          },
          {
            "name": "units",
            "in": "query",
            "required": false,
            "description": "Unit count. When greater than zero, price per unit is returned.",
            "schema": { "type": "number", "minimum": 0, "default": 0 },
            "example": 100
          }
        ],
        "responses": {
          "200": {
            "description": "Calculated cap rate.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "inputs": { "type": "object" },
                    "result": {
                      "type": "object",
                      "properties": {
                        "capRate": {
                          "type": ["number", "null"],
                          "description": "Decimal ratio, e.g. 0.07125."
                        },
                        "capRatePercent": { "type": ["number", "null"] },
                        "pricePerUnit": {
                          "type": ["number", "null"],
                          "description": "Null when units is zero or absent."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/loan-payment": {
      "get": {
        "operationId": "calculateLoanPayment",
        "tags": ["debt"],
        "summary": "Loan payment and debt service",
        "description": "Level monthly payment on a fully-amortizing fixed-rate loan, or the interest-only equivalent.",
        "parameters": [
          {
            "name": "principal",
            "in": "query",
            "required": true,
            "description": "Loan amount in dollars.",
            "schema": { "type": "number", "minimum": 0 },
            "example": 1000000
          },
          {
            "name": "rate",
            "in": "query",
            "required": true,
            "description": "Annual interest rate as a percent, e.g. 6.5.",
            "schema": { "type": "number", "minimum": 0, "maximum": 25 },
            "example": 6.5
          },
          {
            "name": "years",
            "in": "query",
            "required": false,
            "description": "Amortization period in years.",
            "schema": { "type": "number", "minimum": 1, "maximum": 40, "default": 30 },
            "example": 30
          },
          {
            "name": "interestOnly",
            "in": "query",
            "required": false,
            "description": "When true, principal balloons at term end.",
            "schema": { "type": "boolean", "default": false }
          }
        ],
        "responses": {
          "200": {
            "description": "Calculated payment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "inputs": { "type": "object" },
                    "result": {
                      "type": "object",
                      "properties": {
                        "monthlyPayment": { "type": ["number", "null"] },
                        "annualDebtService": { "type": ["number", "null"] },
                        "totalInterest": { "type": ["number", "null"] }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/amortization": {
      "get": {
        "operationId": "calculateAmortization",
        "tags": ["debt"],
        "summary": "Year-by-year amortization schedule",
        "description": "Annual rollup of principal and interest, with optional extra monthly payment to model accelerated payoff.",
        "parameters": [
          {
            "name": "principal",
            "in": "query",
            "required": true,
            "schema": { "type": "number", "minimum": 0 },
            "example": 1000000
          },
          {
            "name": "rate",
            "in": "query",
            "required": true,
            "description": "Annual interest rate as a percent.",
            "schema": { "type": "number", "minimum": 0, "maximum": 25 },
            "example": 6.5
          },
          {
            "name": "years",
            "in": "query",
            "required": false,
            "schema": { "type": "number", "minimum": 1, "maximum": 40, "default": 30 }
          },
          {
            "name": "extraMonthly",
            "in": "query",
            "required": false,
            "description": "Additional principal paid each month.",
            "schema": { "type": "number", "minimum": 0, "default": 0 },
            "example": 500
          }
        ],
        "responses": {
          "200": {
            "description": "Amortization schedule.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "inputs": { "type": "object" },
                    "result": {
                      "type": "object",
                      "properties": {
                        "basePayment": { "type": ["number", "null"] },
                        "totalInterest": { "type": ["number", "null"] },
                        "interestSaved": { "type": ["number", "null"] },
                        "payoffMonths": { "type": ["number", "null"] },
                        "payoffYears": { "type": ["number", "null"] },
                        "payoffRemainderMonths": { "type": ["number", "null"] },
                        "schedule": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "year": { "type": "number" },
                              "begin": { "type": ["number", "null"] },
                              "principal": { "type": ["number", "null"] },
                              "interest": { "type": ["number", "null"] },
                              "end": { "type": ["number", "null"] }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/irr": {
      "get": {
        "operationId": "calculateIrr",
        "tags": ["returns"],
        "summary": "Internal rate of return",
        "description": "Per-period IRR for a series of cash flows, solved by bisection. Returns null when no rate can be bracketed.",
        "parameters": [
          {
            "name": "cashflows",
            "in": "query",
            "required": true,
            "description": "Comma-separated cash flows. The first entry is period 0 and is normally the negative equity outflow.",
            "schema": { "type": "string" },
            "example": "-1000000,80000,85000,90000,95000,1400000"
          }
        ],
        "responses": {
          "200": {
            "description": "Calculated IRR.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "inputs": { "type": "object" },
                    "result": {
                      "type": "object",
                      "properties": {
                        "irr": { "type": ["number", "null"] },
                        "irrPercent": { "type": ["number", "null"] }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/napkin": {
      "get": {
        "operationId": "screenDeal",
        "tags": ["underwriting", "returns"],
        "summary": "Back-of-the-napkin deal screen",
        "description": "Full quick screen from income through exit: EGI, NOI, value, loan sizing, DSCR, year-one cash flow, cash-on-cash, IRR, and equity multiple. Assumes a level fixed-rate loan, expenses held at the entered ratio, NOI growing at the entered rate, and a sale priced on forward-year NOI at the exit cap rate. Excludes capital expenditures, income taxes, and reserves.",
        "parameters": [
          {
            "name": "grossRent",
            "in": "query",
            "required": true,
            "description": "Annual gross rental revenue in dollars.",
            "schema": { "type": "number", "minimum": 0 },
            "example": 1200000
          },
          {
            "name": "capRate",
            "in": "query",
            "required": true,
            "description": "Going-in cap rate as a percent.",
            "schema": { "type": "number", "minimum": 0.01, "maximum": 25 },
            "example": 6.5
          },
          {
            "name": "otherIncome",
            "in": "query",
            "schema": { "type": "number", "minimum": 0, "default": 0 },
            "example": 60000
          },
          {
            "name": "vacancy",
            "in": "query",
            "description": "Vacancy as a percent of gross rent.",
            "schema": { "type": "number", "minimum": 0, "maximum": 100, "default": 5 }
          },
          {
            "name": "expenseRatio",
            "in": "query",
            "description": "Operating expenses as a percent of EGI. Class A ~37, Class B ~43, Class C ~50.",
            "schema": { "type": "number", "minimum": 0, "maximum": 100, "default": 43 }
          },
          {
            "name": "units",
            "in": "query",
            "description": "Unit count, for price per unit.",
            "schema": { "type": "number", "minimum": 0, "default": 0 },
            "example": 100
          },
          {
            "name": "ltv",
            "in": "query",
            "description": "Loan-to-value as a percent.",
            "schema": { "type": "number", "minimum": 0, "maximum": 100, "default": 70 }
          },
          {
            "name": "rate",
            "in": "query",
            "description": "Loan interest rate as a percent.",
            "schema": { "type": "number", "minimum": 0, "maximum": 25, "default": 6.75 }
          },
          {
            "name": "amortYears",
            "in": "query",
            "schema": { "type": "number", "minimum": 1, "maximum": 40, "default": 30 }
          },
          {
            "name": "acqCost",
            "in": "query",
            "description": "Acquisition costs as a percent of price, added to equity invested.",
            "schema": { "type": "number", "minimum": 0, "maximum": 20, "default": 2 }
          },
          {
            "name": "holdYears",
            "in": "query",
            "schema": { "type": "number", "minimum": 1, "maximum": 30, "default": 5 }
          },
          {
            "name": "noiGrowth",
            "in": "query",
            "description": "Annual NOI growth as a percent.",
            "schema": { "type": "number", "minimum": -20, "maximum": 20, "default": 3 }
          },
          {
            "name": "exitCap",
            "in": "query",
            "description": "Exit cap rate as a percent. Defaults to the going-in cap rate.",
            "schema": { "type": "number", "minimum": 0.01, "maximum": 25 }
          },
          {
            "name": "sellingCost",
            "in": "query",
            "description": "Selling costs as a percent of sale price.",
            "schema": { "type": "number", "minimum": 0, "maximum": 15, "default": 2.5 }
          }
        ],
        "responses": {
          "200": {
            "description": "Deal screen.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "inputs": { "type": "object" },
                    "result": {
                      "type": "object",
                      "properties": {
                        "egi": { "type": ["number", "null"] },
                        "noi": { "type": ["number", "null"] },
                        "value": { "type": ["number", "null"] },
                        "pricePerUnit": { "type": ["number", "null"] },
                        "loan": { "type": ["number", "null"] },
                        "equity": { "type": ["number", "null"] },
                        "annualDebtService": { "type": ["number", "null"] },
                        "dscr": { "type": ["number", "null"] },
                        "year1CashFlow": { "type": ["number", "null"] },
                        "cashOnCash": { "type": ["number", "null"] },
                        "cashOnCashPercent": { "type": ["number", "null"] },
                        "exitValue": { "type": ["number", "null"] },
                        "netSaleProceeds": { "type": ["number", "null"] },
                        "equityMultiple": { "type": ["number", "null"] },
                        "irr": { "type": ["number", "null"] },
                        "irrPercent": { "type": ["number", "null"] }
                      }
                    },
                    "assumptions": { "type": "string" },
                    "disclaimer": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/roi": {
      "get": {
        "operationId": "estimateRoi",
        "tags": ["service"],
        "summary": "Analyst time and cost avoided using KeptDo",
        "description": "Estimates annual analyst hours and dollars saved versus a 40-hour manual underwrite, net of a KeptDo Pro subscription.",
        "parameters": [
          {
            "name": "dealsPerYear",
            "in": "query",
            "required": true,
            "schema": { "type": "number", "minimum": 1, "maximum": 1000 },
            "example": 30
          },
          {
            "name": "hourlyRate",
            "in": "query",
            "required": false,
            "description": "Blended analyst cost per hour in dollars.",
            "schema": { "type": "number", "minimum": 1, "default": 150 }
          }
        ],
        "responses": {
          "200": {
            "description": "Savings estimate.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "inputs": { "type": "object" },
                    "result": {
                      "type": "object",
                      "properties": {
                        "hoursSaved": { "type": ["number", "null"] },
                        "dollarsSaved": { "type": ["number", "null"] },
                        "subscriptionCost": { "type": ["number", "null"] },
                        "netSavings": { "type": ["number", "null"] },
                        "roiMultiple": { "type": ["number", "null"] }
                      }
                    },
                    "assumptions": { "type": "object" },
                    "disclaimer": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    }
  },
  "components": {
    "responses": {
      "BadRequest": {
        "description": "A parameter was missing, non-numeric, or out of range. The message names the offending parameter.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": { "type": "string", "const": "bad_request" },
                "message": { "type": "string" }
              },
              "required": ["error", "message"]
            },
            "example": {
              "error": "bad_request",
              "message": "Missing required parameter \"capRate\"."
            }
          }
        }
      }
    }
  }
}
