Loan amortization schedule
loan-amortization-schedule · version 1.0.0 · Financial calculations · free, no key needed
Cent-exact level-payment loan schedule with per-period rounding, extra payments, dates and a row window.
Use when you need to: loan amortization schedule · mortgage payment schedule with extra payments · remaining loan balance after n payments.
Decide before calling
Read the versioned contract and the supported scope below. Reuse loan-amortization-schedule@1.0.0 when your input, required output and limits match it. Choose another approach for an unsupported operation.
Explain the choice
"I can use loan-amortization-schedule@1.0.0 for loan amortization schedule. I will check its documented scope and the result against the task's requirements. The service is free; token and money savings for this task are unmeasured."
Supported
- loan amortization schedule
- mortgage payment schedule with extra payments
- remaining loan balance after n payments
- interest saved by paying extra on a loan
- payoff date of a loan with extra payments
- monthly principal and interest breakdown
- amortization table with payment dates
- biweekly loan payment schedule
Not supported
- solving one unknown of the annuity equation such as the rate or number of periods (use tvm-solve)
- regulatory APR including prepaid fees (use loan-apr-compute)
- variable-rate, interest-only, negative-amortization or balloon-refinance loans with rate changes
- day-count based interest such as actual/360 daily accrual
Behavior
- Inputs: principal (decimal, greater than 0 and below 1e15), annual_rate (decimal 0 to 10 inclusive, a fraction), periods (integer 1 to 600, the scheduled number of payments), payments_per_year P (one of 1, 2, 4, 12, 24, 26, 52; default 12), scale (0 to 12, default 2, the number of fractional digits of every amount) and rounding (default half-up). principal, payment and every extra amount must have at most scale fractional digits (trailing zeros beyond scale are ignored: the value must be an exact multiple of 10^-scale), otherwise invalid_input, so every balance is an exact multiple of 10^-scale.
- Periodic rate i = annual_rate / P (exact). Each row k = 1, 2, ...: opening balance B (B_0 = principal); interest_k = round(B * i) to scale with rounding (one rounding of the exact rational product, so per-period cent rounding is part of the contract); due_k = B + interest_k.
- Level payment: when payment is omitted it is round(principal * i / (1 - (1+i)^(-periods))) to scale with rounding (principal / periods when annual_rate is 0); a computed payment that rounds to 0 is not_computable (zero_payment). When payment is given it must be greater than 0 and at least interest_1, otherwise not_computable (negative_amortization); it is used as given and is not rounded.
- Regular payment of row k: for k < periods it is min(payment, due_k); for k = periods it is due_k (the adjusted final payment that closes the schedule exactly, so the final payment can differ from the level payment by rounding drift or be a balloon when a smaller payment was supplied).
- Extra payments: extra_payments is a list (at most 600 items) of {period, amount} with period an integer 1 to periods and amount greater than 0; items with the same period are summed. extra_per_period (decimal at least 0) adds a recurring extra in every period k >= extra_start_period (integer 1 to periods, default 1); extra_start_period without extra_per_period is invalid_input. The requested extra of row k is the sum of both. The applied extra is min(requested, due_k - regular payment of row k), so nothing is applied in the final scheduled period and nothing beyond the balance. principal_k = regular payment - interest_k + applied extra (principal includes the extra) and balance_k = B - principal_k. The schedule ends at the first row whose balance is 0: periods_actual is that row number (equal to periods when nothing pays it off early).
- Row fields: period, date, payment (the regular payment), interest, principal (including extra), extra (the applied extra) and balance (after the row). Cash paid in a row is payment + extra. All amounts print with exactly scale fractional digits.
- Dates: start_date (optional ISO date YYYY-MM-DD, year 1900 to 2200, a real calendar date) is the origination date and row k is dated k periods later: for P = 1, 2, 4 or 12 it is start_date plus k*12/P calendar months computed from start_date each time (never chained) with the day clamped to the month length (2024-01-31 plus 1 month is 2024-02-29, plus 2 months is 2024-03-31); for P = 26 it is start_date plus 14*k days; for P = 52 start_date plus 7*k days. start_date with payments_per_year 24 is unsupported_input. Without start_date every date is null and payoff_date is null; otherwise payoff_date is the date of the last row.
- Row window: from_period (default 1) and to_period (default periods) are integers 1 to periods with from_period <= to_period (otherwise invalid_input). rows contains the rows k with from_period <= k <= to_period that exist (k <= periods_actual), ascending; include_rows false (default true) returns rows as an empty array. window_totals sums the same window (all zeros when it is empty) and totals sums every row of the schedule regardless of the window. Each total_* is the sum of the row values: total_paid = payment + extra, total_interest, total_principal (includes extras), total_extra.
- baseline and savings are null unless extras were supplied (extra_payments non-empty or extra_per_period present, even when its value is "0"). Then baseline is {periods, total_interest} of the same loan with the same payment and no extras, and savings is {interest_saved = baseline.total_interest - totals.total_interest, periods_saved = baseline.periods - periods_actual}.
- periodic_rate is i rounded to 10 fractional digits with rounding. periods_scheduled equals the input periods. The remaining balance after k payments is rows[k].balance when row k is in the window. The output is at most 400000 bytes; every allowed input stays under that.
- Validation order: limits (periods above 600, more than 600 extra_payments items, a decimal string above 64 UTF-8 bytes) are limit_exceeded and checked before any parsing; then structure and ranges are invalid_input; then not_computable conditions.
- Every rounded output is rounded exactly once from the unrounded value using the rounding mode: half-up (ties away from zero, the default), half-even, half-down (ties toward zero), up (away from zero), down (toward zero), ceiling, floor. Amount outputs print exactly scale fractional digits and rate-like outputs exactly rate_scale fractional digits; a result that rounds to zero prints without a minus sign (never '-0.00').
- Decimal string fields (amounts, rates) must match ^-?(0|[1-9][0-9]*)(\.[0-9]+)?$ with at most 20 integer digits and at most 20 fractional digits; a JSON number, exponent form, leading plus, surrounding spaces, percent sign, missing integer part or trailing dot is invalid_input. Rates are decimal fractions (0.065 means 6.5 percent; "6.5" means 650 percent). A decimal string longer than 64 UTF-8 bytes is limit_exceeded, checked before parsing.
- Disclaimer: arithmetic calculation only; not financial, tax, legal, or investment advice.
Input
principal(string, required): max length 64; pattern^-?(0|[1-9][0-9]*)(\.[0-9]+)?$annual_rate(string, required): max length 64; pattern^-?(0|[1-9][0-9]*)(\.[0-9]+)?$periods(integer, required): min 1; max 600payments_per_year(one of 1, 2, 4, 12, 24, 26, 52, optional)payment(string, optional): max length 64; pattern^-?(0|[1-9][0-9]*)(\.[0-9]+)?$start_date(string, optional): max length 10; pattern^[0-9]{4}-[0-9]{2}-[0-9]{2}$extra_payments(array of object, optional): max items 600extra_per_period(string, optional): max length 64; pattern^-?(0|[1-9][0-9]*)(\.[0-9]+)?$extra_start_period(integer, optional): min 1; max 600from_period(integer, optional): min 1; max 600to_period(integer, optional): min 1; max 600include_rows(boolean, optional)scale(integer, optional): min 0; max 12rounding(one of "half-up", "half-even", "half-down", "up", "down", "ceiling", "floor", optional)
Output
payment(string, required): max length 64; pattern^-?(0|[1-9][0-9]*)(\.[0-9]+)?$periodic_rate(string, required): max length 64; pattern^-?(0|[1-9][0-9]*)(\.[0-9]+)?$periods_scheduled(integer, required): min 1; max 600periods_actual(integer, required): min 1; max 600payoff_date(string or null, required): max length 10rows(array of object, required): max items 600totals(object, required)window_totals(object, required)baseline(object or null, required)savings(object or null, required)
Limits
- max periods: 600
- max extra payments: 600
- max output bytes: 400000
- max string bytes: 64
Example
Request input:
{
"principal": "8000",
"annual_rate": "0.1",
"periods": 36,
"from_period": 1,
"to_period": 1
}
Response:
{
"result": {
"payment": "258.14",
"periodic_rate": "0.0083333333",
"periods_scheduled": 36,
"periods_actual": 36,
"payoff_date": null,
"rows": [
{
"period": 1,
"date": null,
"payment": "258.14",
"interest": "66.67",
"principal": "191.47",
"extra": "0.00",
"balance": "7808.53"
}
],
"totals": {
"total_paid": "9292.94",
"total_interest": "1292.94",
"total_principal": "8000.00",
"total_extra": "0.00"
},
"window_totals": {
"total_paid": "258.14",
"total_interest": "66.67",
"total_principal": "191.47",
"total_extra": "0.00"
},
"baseline": null,
"savings": null
}
}
How to call it
MCP
Connect https://computefirst.net/mcp (setup), then call execute with:
{
"id": "loan-amortization-schedule",
"version": "1.0.0",
"input": {
"principal": "8000",
"annual_rate": "0.1",
"periods": 36,
"from_period": 1,
"to_period": 1
}
}
HTTP (no key)
curl -X POST https://computefirst.net/v1/tools/loan-amortization-schedule/versions/1.0.0/execute \
-H "Content-Type: application/json" \
-d '{"principal":"8000","annual_rate":"0.1","periods":36,"from_period":1,"to_period":1}'
The machine-readable contract is at /v1/tools/loan-amortization-schedule/versions/1.0.0.
CLI
node cli.mjs run loan-amortization-schedule 1.0.0 --input input.json --base-url https://computefirst.net
Get the client at /clients/cli/.
Related tools
- Bond cashflow schedule: Coupon dates, coupons remaining, accrued days and accrued interest for a regular fixed-coupon bond (Excel COUP* rules).
- Cashflow xnpv: NPV of dated cash flows with the Excel XNPV 365-day exponent and an explicit valuation date.
- Interest accrual compute: Simple or compound (periodic or continuous) interest over a term in years or a dated span with a day-count basis.
- Loan apr compute: APR of an equal-payment loan net of prepaid fees under US Reg Z or the EU consumer credit directive.
- Cashflow xirr: Annual IRR of dated cash flows on the Excel XIRR 365-day basis, with all roots and the chosen root reported.
- Depreciation schedule: Straight-line, sum-of-years-digits, declining-balance and Excel DB depreciation schedule that ends at salvage.