# 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](/v1/tools/loan-amortization-schedule/versions/1.0.0) 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 600
- `payments_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 600
- `extra_per_period` (string, optional): max length 64; pattern `^-?(0|[1-9][0-9]*)(\.[0-9]+)?$`
- `extra_start_period` (integer, optional): min 1; max 600
- `from_period` (integer, optional): min 1; max 600
- `to_period` (integer, optional): min 1; max 600
- `include_rows` (boolean, optional)
- `scale` (integer, optional): min 0; max 12
- `rounding` (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 600
- `periods_actual` (integer, required): min 1; max 600
- `payoff_date` (string or null, required): max length 10
- `rows` (array of object, required): max items 600
- `totals` (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:

```json
{
  "principal": "8000",
  "annual_rate": "0.1",
  "periods": 36,
  "from_period": 1,
  "to_period": 1
}
```

Response:

```json
{
  "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](/docs#connect)), then call `execute` with:

```json
{
  "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)

```sh
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](/v1/tools/loan-amortization-schedule/versions/1.0.0).

### CLI

```sh
node cli.mjs run loan-amortization-schedule 1.0.0 --input input.json --base-url https://computefirst.net
```

Get the client at [/clients/cli/](/clients/cli/).

## Related tools

- [Bond cashflow schedule](/tools/bond-cashflow-schedule): Coupon dates, coupons remaining, accrued days and accrued interest for a regular fixed-coupon bond (Excel COUP* rules).
- [Cashflow xnpv](/tools/cashflow-xnpv): NPV of dated cash flows with the Excel XNPV 365-day exponent and an explicit valuation date.
- [Interest accrual compute](/tools/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](/tools/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](/tools/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](/tools/depreciation-schedule): Straight-line, sum-of-years-digits, declining-balance and Excel DB depreciation schedule that ends at salvage.
