{
  "openapi": "3.0.1",
  "info": {
    "title": "Finology Public API",
    "description": "Verified-engine calculations over the Finology student-loan engine. Calc-only: every endpoint computes from the inputs in the request; no stored client data is exposed. Authenticate with an API key in the X-Api-Key header.",
    "version": "v1"
  },
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "Finology.PublicApi"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "tags": [
          "Finology.PublicApi"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/calculators/loan": {
      "post": {
        "tags": [
          "Finology.PublicApi"
        ],
        "summary": "Keyless multi-plan federal loan calculator",
        "description": "Computes the first-year payment, lifetime cost, forgiveness and forgiveness tax for every federal repayment plan a borrower is eligible for, from the same verified engine as the paid API. No API key required. This free rung returns no verifiable answer id — the warranted, logged answer is the keyed /v1/projection and /v1/comparison surface.",
        "operationId": "KeylessLoanCalculator",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KeylessLoanRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LoanCalcResult"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/v1/verify/{answerId}": {
      "get": {
        "tags": [
          "Finology.PublicApi"
        ],
        "summary": "Confirm that an answer was recorded",
        "description": "Looks up the answer-of-record row behind a provenance.answerId and returns when it was recorded, which endpoint served it, the rule ledger version and the inputs hash. Anonymous; never returns the inputs, the caller or the tenant.",
        "operationId": "VerifyAnswer",
        "parameters": [
          {
            "name": "answerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifiedAnswer"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/v1/rules": {
      "get": {
        "tags": [
          "Finology.PublicApi"
        ],
        "summary": "The rules ledger behind every answer",
        "description": "Every constant the engine uses, with its value, primary source, effective date and last-verified date; the rules the engine knowingly does not apply; and the ledger version stamped into every answer. Anonymous. ETag is the ledger version.",
        "operationId": "GetRules",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RulesResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/rules/version": {
      "get": {
        "tags": [
          "Finology.PublicApi"
        ],
        "summary": "The current rules ledger version",
        "description": "Poll this. When it changes, a constant moved; GET /v1/rules says which.",
        "operationId": "GetRulesVersion",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/keys/sandbox": {
      "post": {
        "tags": [
          "Finology.PublicApi"
        ],
        "summary": "Issue a free sandbox API key",
        "description": "Mints a Sandbox-tier key for the supplied email address, valid 30 days and capped at 25 calls/month, usable on this API and on the MCP server at mcp.finology.tech/mcp. The key is returned once and cannot be recovered. Requesting again for the same address revokes the previous key.",
        "operationId": "IssueSandboxKey",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SandboxKeyRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxKeyResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/v1/keys/claim": {
      "post": {
        "tags": [
          "Finology.PublicApi"
        ],
        "summary": "Collect a paid production key with a sandbox key",
        "description": "After the operator subscribes on the Payment Link with the same email address the sandbox key was issued to, present the sandbox key here to receive the production key directly. The key delivered by email is revoked, unless it has already been used, in which case this returns 409.",
        "operationId": "ClaimProductionKey",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClaimKeyRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClaimKeyResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projection": {
      "post": {
        "tags": [
          "Finology.PublicApi"
        ],
        "summary": "Run one verified repayment projection",
        "description": "Computes the year-1 payment, payoff/forgiveness date, amount forgiven, forgiveness tax, total out-of-pocket and weighted-average rate for the supplied loans under one repayment plan. Calc-only; no stored client data is read or returned. Send \"includeYearly\": true to add `yearly` to each projection: year 0 is the loan book as sent; year n covers repayment months 12(n-1) to 12n-1 and gives the principal and interest balance after that year's last payment, the payments that year, the cumulative paid and any balance forgiven that year (date = that month). The series ends at the payoff or forgiveness month. Payments sum to totalOutOfPocket and forgiven amounts to amountForgiven. Money in cents. Default false: the field is then absent and the response is unchanged. A plan whose answer is an artifact of the prior-month count (nothing left to pay, the whole balance in one payment, or forgiveness before any payment) is a 400 naming the plan, the same plans the free calculator at /v1/calculators/loan leaves out.",
        "operationId": "RunProjection",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectionResult"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/v1/comparison": {
      "post": {
        "tags": [
          "Finology.PublicApi"
        ],
        "summary": "Compare repayment scenarios side-by-side",
        "description": "Runs several scenarios (commonly the same loans under different plans) and returns each verified projection plus the engine-decided lowest-total-cost label: the arm with the lowest totalOutOfPocket + forgivenessTaxesOwed. Calc-only. Send \"includeYearly\": true to add `yearly` to each projection: year 0 is the loan book as sent; year n covers repayment months 12(n-1) to 12n-1 and gives the principal and interest balance after that year's last payment, the payments that year, the cumulative paid and any balance forgiven that year (date = that month). The series ends at the payoff or forgiveness month. Payments sum to totalOutOfPocket and forgiven amounts to amountForgiven. Money in cents. Default false: the field is then absent and the response is unchanged. A plan whose answer is an artifact of the prior-month count (nothing left to pay, the whole balance in one payment, or forgiveness before any payment) is a 400 naming the plan, the same plans the free calculator at /v1/calculators/loan leaves out.",
        "operationId": "CompareScenarios",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ComparisonRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScenarioComparisonResult"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/v1/plan-eligibility": {
      "post": {
        "tags": [
          "Finology.PublicApi"
        ],
        "summary": "Eligible repayment plans per loan",
        "description": "Returns the eligible repayment plan types for each supplied loan, using the same eligibility logic the planner's plan picker uses. Every plan returned is a planType /v1/projection and /v1/comparison accept. Calc-only.",
        "operationId": "GetEligiblePlans",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EligibilityRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EligibilityResult"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/v1/reference": {
      "get": {
        "tags": [
          "Finology.PublicApi"
        ],
        "summary": "Canonical scenarios computed live, each recorded and verifiable",
        "description": "Returns the portable scenarios of Finology's parity oracle — single, joint, PSLF-midway and prior-IDR-months federal books under RAP, IBR and Standard — computed on the current engine, each with the exact request to replay it through POST /v1/comparison and a verify URL for its answer-of-record row. Calc-only; no stored data.",
        "operationId": "GetReferenceScenarios",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferenceResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/plan-types": {
      "get": {
        "tags": [
          "Finology.PublicApi"
        ],
        "summary": "List accepted repayment plan-type strings",
        "operationId": "GetPlanTypes",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "tags": [
          "Finology.PublicApi"
        ],
        "summary": "The account, plan and this month's usage behind the presented key (no secrets)",
        "description": "Returns the tenant, tier and key prefix, plus the plan name, the monthly call limit, calls used and remaining this period (this call included), and when the count resets (00:00 UTC on the 1st). The same figures are on every keyed response as RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until the reset).",
        "operationId": "WhoAmI",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/records": {
      "get": {
        "tags": [
          "Finology.PublicApi"
        ],
        "summary": "Your account's answer-of-record rows",
        "description": "Every answer recorded under the calling key's account, newest first. Filter with from/to (ISO 8601); page with the returned nextCursor. Each row carries the verify URL anyone can open for that answer.",
        "operationId": "ListRecords",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecordsPage"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ClaimKeyRequest": {
        "required": [
          "sandboxKey"
        ],
        "type": "object",
        "properties": {
          "sandboxKey": {
            "minLength": 1,
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "ClaimKeyResponse": {
        "type": "object",
        "properties": {
          "apiKey": {
            "type": "string",
            "nullable": true
          },
          "keyPrefix": {
            "type": "string",
            "nullable": true
          },
          "tier": {
            "type": "string",
            "nullable": true
          },
          "monthlyCallLimit": {
            "type": "integer",
            "format": "int64"
          },
          "headerName": {
            "type": "string",
            "nullable": true
          },
          "mcpEndpoint": {
            "type": "string",
            "nullable": true
          },
          "revokedKeyPrefix": {
            "type": "string",
            "nullable": true
          },
          "notice": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ComparisonArm": {
        "required": [
          "loans",
          "scenario"
        ],
        "type": "object",
        "properties": {
          "scenario": {
            "$ref": "#/components/schemas/ScenarioRequest"
          },
          "loans": {
            "maxItems": 100,
            "minItems": 1,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LoanRequest"
            }
          }
        },
        "additionalProperties": false
      },
      "ComparisonRequest": {
        "required": [
          "arms"
        ],
        "type": "object",
        "properties": {
          "arms": {
            "maxItems": 10,
            "minItems": 2,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ComparisonArm"
            }
          },
          "includeYearly": {
            "type": "boolean",
            "description": "Optional, default false. Send \"includeYearly\": true to add `yearly` to each projection: year 0 is the loan book as sent; year n covers repayment months 12(n-1) to 12n-1 and gives the principal and interest balance after that year's last payment, the payments that year, the cumulative paid and any balance forgiven that year (date = that month). The series ends at the payoff or forgiveness month. Payments sum to totalOutOfPocket and forgiven amounts to amountForgiven. Money in cents. Default false: the field is then absent and the response is unchanged.",
            "default": false
          }
        },
        "additionalProperties": false
      },
      "EligibilityRequest": {
        "required": [
          "loans"
        ],
        "type": "object",
        "properties": {
          "loans": {
            "maxItems": 100,
            "minItems": 1,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LoanRequest"
            }
          }
        },
        "additionalProperties": false
      },
      "EligibilityResult": {
        "type": "object",
        "properties": {
          "loans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LoanEligibility"
            },
            "nullable": true
          },
          "provenance": {
            "$ref": "#/components/schemas/EngineProvenance"
          }
        },
        "additionalProperties": false
      },
      "EngineProvenance": {
        "type": "object",
        "properties": {
          "computedBy": {
            "type": "string",
            "nullable": true
          },
          "engine": {
            "type": "string",
            "nullable": true
          },
          "parityVerified": {
            "type": "boolean"
          },
          "computedAtUtc": {
            "type": "string",
            "format": "date-time"
          },
          "ruleVersion": {
            "type": "string",
            "nullable": true
          },
          "citations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RuleCitation"
            },
            "nullable": true
          },
          "knownOmissions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RuleOmissionNote"
            },
            "nullable": true
          },
          "answerId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "verifyUrl": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "HttpValidationProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "instance": {
            "type": "string",
            "nullable": true
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "nullable": true
          }
        },
        "additionalProperties": { }
      },
      "KeylessLoanRequest": {
        "required": [
          "balance",
          "borrowerAgi",
          "ratePct"
        ],
        "type": "object",
        "properties": {
          "balance": {
            "type": "number",
            "format": "double"
          },
          "ratePct": {
            "type": "number",
            "format": "double"
          },
          "borrowerAgi": {
            "type": "number",
            "format": "double"
          },
          "filingStatus": {
            "type": "string",
            "nullable": true
          },
          "spouseAgi": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "spouseFederalLoanBalance": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "dependents": {
            "type": "integer",
            "format": "int32"
          },
          "loanType": {
            "type": "string",
            "nullable": true
          },
          "state": {
            "type": "string",
            "nullable": true
          },
          "pursuingPslf": {
            "type": "boolean"
          },
          "pslfCreditedMonths": {
            "type": "integer",
            "format": "int32"
          },
          "priorIdrMonths": {
            "type": "integer",
            "format": "int32"
          },
          "incomeGrowthPct": {
            "type": "number",
            "format": "double"
          },
          "borrowedOnOrAfterJul12026": {
            "type": "boolean",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "LoanCalcExcludedPlan": {
        "type": "object",
        "properties": {
          "plan": {
            "type": "string",
            "nullable": true
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "reason": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "LoanCalcPlan": {
        "type": "object",
        "properties": {
          "plan": {
            "type": "string",
            "nullable": true
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "monthlyPayment": {
            "type": "number",
            "format": "double"
          },
          "totalPaid": {
            "type": "number",
            "format": "double"
          },
          "monthsToPayoff": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "forgivenBalance": {
            "type": "number",
            "format": "double"
          },
          "forgivenessAtMonth": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "taxOnForgiveness": {
            "type": "number",
            "format": "double"
          },
          "lifetimeCost": {
            "type": "number",
            "format": "double"
          },
          "forgivenessType": {
            "type": "string",
            "nullable": true
          },
          "parityVerified": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "LoanCalcResult": {
        "type": "object",
        "properties": {
          "asOf": {
            "type": "string",
            "nullable": true
          },
          "lowestMonthlyPlan": {
            "type": "string",
            "nullable": true
          },
          "lowestLifetimeCostPlan": {
            "type": "string",
            "nullable": true
          },
          "plans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LoanCalcPlan"
            },
            "nullable": true
          },
          "excludedPlans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LoanCalcExcludedPlan"
            },
            "nullable": true
          },
          "computedBy": {
            "type": "string",
            "nullable": true
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "LoanEligibility": {
        "type": "object",
        "properties": {
          "loanType": {
            "type": "string",
            "nullable": true
          },
          "balance": {
            "type": "number",
            "format": "double"
          },
          "eligiblePlanTypes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "LoanRequest": {
        "required": [
          "interestRate",
          "loanType",
          "principalBalance"
        ],
        "type": "object",
        "properties": {
          "loanType": {
            "minLength": 1,
            "type": "string"
          },
          "principalBalance": {
            "maximum": 100000000,
            "minimum": 0,
            "type": "number",
            "format": "double"
          },
          "interestBalance": {
            "maximum": 100000000,
            "minimum": 0,
            "type": "number",
            "format": "double"
          },
          "interestRate": {
            "maximum": 1,
            "minimum": 0,
            "type": "number",
            "format": "double"
          },
          "disbursedAmount": {
            "maximum": 100000000,
            "minimum": 0,
            "type": "number",
            "format": "double"
          },
          "idrMonthsInRepayment": {
            "maximum": 600,
            "minimum": 0,
            "type": "integer",
            "format": "int32"
          },
          "pslfCount": {
            "maximum": 120,
            "minimum": 0,
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "MeResponse": {
        "type": "object",
        "properties": {
          "tenant": {
            "type": "string",
            "nullable": true
          },
          "tier": {
            "type": "string",
            "nullable": true
          },
          "keyPrefix": {
            "type": "string",
            "nullable": true
          },
          "plan": {
            "type": "string",
            "nullable": true
          },
          "monthlyCallLimit": {
            "type": "integer",
            "format": "int64"
          },
          "callsUsedThisPeriod": {
            "type": "integer",
            "format": "int64"
          },
          "callsRemaining": {
            "type": "integer",
            "format": "int64"
          },
          "periodStart": {
            "type": "string",
            "format": "date-time"
          },
          "periodResetsAt": {
            "type": "string",
            "format": "date-time"
          },
          "plansPage": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "instance": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": { }
      },
      "ProjectionRequest": {
        "required": [
          "loans",
          "scenario"
        ],
        "type": "object",
        "properties": {
          "scenario": {
            "$ref": "#/components/schemas/ScenarioRequest"
          },
          "loans": {
            "maxItems": 100,
            "minItems": 1,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LoanRequest"
            }
          },
          "includeYearly": {
            "type": "boolean",
            "description": "Optional, default false. Send \"includeYearly\": true to add `yearly` to each projection: year 0 is the loan book as sent; year n covers repayment months 12(n-1) to 12n-1 and gives the principal and interest balance after that year's last payment, the payments that year, the cumulative paid and any balance forgiven that year (date = that month). The series ends at the payoff or forgiveness month. Payments sum to totalOutOfPocket and forgiven amounts to amountForgiven. Money in cents. Default false: the field is then absent and the response is unchanged.",
            "default": false
          }
        },
        "additionalProperties": false
      },
      "ProjectionResult": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "nullable": true
          },
          "planType": {
            "type": "string",
            "nullable": true
          },
          "year1MonthlyPayment": {
            "type": "number",
            "format": "double"
          },
          "payoffOrForgivenessDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "monthsToFinish": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "totalOutOfPocket": {
            "type": "number",
            "format": "double"
          },
          "amountForgiven": {
            "type": "number",
            "format": "double"
          },
          "forgivenessTaxesOwed": {
            "type": "number",
            "format": "double"
          },
          "forgivenessType": {
            "enum": [
              "Pslf",
              "TimeBased",
              "Teacher",
              "None"
            ],
            "type": "string",
            "description": "Which forgiveness program the projection reaches: Pslf, TimeBased, Teacher or None. Keyed routes wrote the integer before 2026-10-14; that value is still sent as forgivenessTypeCode (0 Pslf, 1 TimeBased, 2 Teacher, 3 None), deprecated."
          },
          "forgivenessTypeCode": {
            "type": "integer",
            "format": "int32",
            "readOnly": true
          },
          "weightedAverageInterestRate": {
            "type": "number",
            "format": "double"
          },
          "yearly": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/YearlyPoint"
            },
            "description": "Present only when the request sent includeYearly: true. Send \"includeYearly\": true to add `yearly` to each projection: year 0 is the loan book as sent; year n covers repayment months 12(n-1) to 12n-1 and gives the principal and interest balance after that year's last payment, the payments that year, the cumulative paid and any balance forgiven that year (date = that month). The series ends at the payoff or forgiveness month. Payments sum to totalOutOfPocket and forgiven amounts to amountForgiven. Money in cents. Default false: the field is then absent and the response is unchanged.",
            "nullable": true
          },
          "provenance": {
            "$ref": "#/components/schemas/EngineProvenance"
          }
        },
        "additionalProperties": false
      },
      "RecordRow": {
        "type": "object",
        "properties": {
          "answerId": {
            "type": "string",
            "format": "uuid"
          },
          "recordedAtUtc": {
            "type": "string",
            "format": "date-time"
          },
          "source": {
            "type": "string",
            "nullable": true
          },
          "endpoint": {
            "type": "string",
            "nullable": true
          },
          "eventType": {
            "type": "string",
            "nullable": true
          },
          "ruleVersion": {
            "type": "string",
            "nullable": true
          },
          "inputsHash": {
            "type": "string",
            "nullable": true
          },
          "keyPrefix": {
            "type": "string",
            "nullable": true
          },
          "verifyUrl": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "RecordsPage": {
        "type": "object",
        "properties": {
          "records": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecordRow"
            },
            "nullable": true
          },
          "nextCursor": {
            "type": "integer",
            "format": "int64",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ReferenceAnswer": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "oracleScenario": {
            "type": "string",
            "nullable": true
          },
          "request": {
            "$ref": "#/components/schemas/ComparisonRequest"
          },
          "answer": {
            "$ref": "#/components/schemas/ScenarioComparisonResult"
          }
        },
        "additionalProperties": false
      },
      "ReferenceResponse": {
        "type": "object",
        "properties": {
          "ruleVersion": {
            "type": "string",
            "nullable": true
          },
          "computedAtUtc": {
            "type": "string",
            "format": "date-time"
          },
          "howToUse": {
            "type": "string",
            "nullable": true
          },
          "scenarios": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReferenceAnswer"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "RuleCitation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "authority": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "effectiveDate": {
            "type": "string",
            "nullable": true
          },
          "lastVerified": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "RuleConstantEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "plan": {
            "type": "string",
            "nullable": true
          },
          "unit": {
            "type": "string",
            "nullable": true
          },
          "value": {
            "nullable": true
          },
          "definition": {
            "type": "string",
            "nullable": true
          },
          "source": {
            "$ref": "#/components/schemas/SourceEntry"
          },
          "effectiveDate": {
            "type": "string",
            "nullable": true
          },
          "lastVerified": {
            "type": "string",
            "nullable": true
          },
          "parityLocked": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "RuleOmissionEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "source": {
            "$ref": "#/components/schemas/SourceEntry"
          },
          "effectiveDate": {
            "type": "string",
            "nullable": true
          },
          "expiresDate": {
            "type": "string",
            "nullable": true
          },
          "lastVerified": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "RuleOmissionNote": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "authority": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "effectiveDate": {
            "type": "string",
            "nullable": true
          },
          "expiresDate": {
            "type": "string",
            "nullable": true
          },
          "lastVerified": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "RulesResponse": {
        "type": "object",
        "properties": {
          "version": {
            "type": "string",
            "nullable": true
          },
          "generatedAtUtc": {
            "type": "string",
            "format": "date-time"
          },
          "constants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RuleConstantEntry"
            },
            "nullable": true
          },
          "knownOmissions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RuleOmissionEntry"
            },
            "nullable": true
          },
          "howToUse": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "SandboxKeyRequest": {
        "required": [
          "email"
        ],
        "type": "object",
        "properties": {
          "email": {
            "minLength": 1,
            "type": "string",
            "format": "email"
          }
        },
        "additionalProperties": false
      },
      "SandboxKeyResponse": {
        "type": "object",
        "properties": {
          "apiKey": {
            "type": "string",
            "nullable": true
          },
          "keyPrefix": {
            "type": "string",
            "nullable": true
          },
          "tier": {
            "type": "string",
            "nullable": true
          },
          "monthlyCallLimit": {
            "type": "integer",
            "format": "int64"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "headerName": {
            "type": "string",
            "nullable": true
          },
          "mcpEndpoint": {
            "type": "string",
            "nullable": true
          },
          "upgradeUrl": {
            "type": "string",
            "nullable": true
          },
          "platformUrl": {
            "type": "string",
            "nullable": true
          },
          "claimHow": {
            "type": "string",
            "nullable": true
          },
          "terms": {
            "type": "string",
            "nullable": true
          },
          "notice": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ScenarioComparisonResult": {
        "type": "object",
        "properties": {
          "projections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProjectionResult"
            },
            "nullable": true
          },
          "lowestCostLabel": {
            "type": "string",
            "nullable": true
          },
          "provenance": {
            "$ref": "#/components/schemas/EngineProvenance"
          }
        },
        "additionalProperties": false
      },
      "ScenarioRequest": {
        "required": [
          "adjustedGrossIncome",
          "dependents",
          "filingStatus",
          "planType",
          "state",
          "workingTowardsPslf"
        ],
        "type": "object",
        "properties": {
          "planType": {
            "minLength": 1,
            "type": "string"
          },
          "adjustedGrossIncome": {
            "maximum": 100000000,
            "minimum": 0,
            "type": "number",
            "format": "double"
          },
          "spouseAdjustedGrossIncome": {
            "maximum": 100000000,
            "minimum": 0,
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "spouseFederalLoanBalance": {
            "maximum": 100000000,
            "minimum": 0,
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "filingStatus": {
            "minLength": 1,
            "type": "string"
          },
          "dependents": {
            "maximum": 30,
            "minimum": 0,
            "type": "integer",
            "format": "int32"
          },
          "state": {
            "minLength": 1,
            "pattern": "^[A-Za-z]{2}$",
            "type": "string"
          },
          "workingTowardsPslf": {
            "type": "boolean"
          },
          "annualIncomeGrowth": {
            "maximum": 0.5,
            "minimum": 0,
            "type": "number",
            "format": "double"
          },
          "label": {
            "maxLength": 80,
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "SourceEntry": {
        "type": "object",
        "properties": {
          "authority": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "note": {
            "type": "string",
            "nullable": true
          },
          "cadence": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "VerifiedAnswer": {
        "type": "object",
        "properties": {
          "answerId": {
            "type": "string",
            "format": "uuid"
          },
          "recordedAtUtc": {
            "type": "string",
            "format": "date-time"
          },
          "source": {
            "type": "string",
            "nullable": true
          },
          "endpoint": {
            "type": "string",
            "nullable": true
          },
          "eventType": {
            "type": "string",
            "nullable": true
          },
          "ruleVersion": {
            "type": "string",
            "nullable": true
          },
          "inputsHash": {
            "type": "string",
            "nullable": true
          },
          "recorded": {
            "type": "boolean",
            "readOnly": true
          }
        },
        "additionalProperties": false
      },
      "YearlyPoint": {
        "type": "object",
        "properties": {
          "year": {
            "type": "integer",
            "description": "0 for the opening balance, then 1, 2, ... per year of repayment.",
            "format": "int32"
          },
          "date": {
            "type": "string",
            "description": "The month the balances are read.",
            "format": "date-time"
          },
          "principalBalance": {
            "type": "number",
            "description": "Outstanding principal across the loan book at date.",
            "format": "double"
          },
          "interestBalance": {
            "type": "number",
            "description": "Outstanding unpaid interest across the loan book at date.",
            "format": "double"
          },
          "paymentsThisYear": {
            "type": "number",
            "description": "What the borrower paid during this year (0 for year 0).",
            "format": "double"
          },
          "cumulativePaid": {
            "type": "number",
            "description": "Paid from the repayment start through this year. The last point equals totalOutOfPocket.",
            "format": "double"
          },
          "forgivenThisYear": {
            "type": "number",
            "description": "Balance forgiven during this year; sums to amountForgiven.",
            "format": "double"
          }
        },
        "additionalProperties": false,
        "description": "One year of a projection. Year 0 is the loan book as sent (date = repayment start). Year n covers repayment months 12(n-1) to 12n-1; balances are after that year's last payment and date is that month. Money in cents, half-up; each year's payment is rounded so the yearly payments add up to totalOutOfPocket exactly."
      }
    },
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "description": "Your Finology API key, e.g. fk_live_...",
        "name": "X-Api-Key",
        "in": "header"
      }
    }
  },
  "security": [
    {
      "ApiKey": [ ]
    }
  ]
}