Skip to main content
Glama

Senaro Personal Finance

Credit Card Payoff Calculator

calculate_cc_payoff
Read-onlyIdempotent

Calculation, not advice. Verify with a professional before acting. Calculate credit card payoff timeline with month-by-month amortization. Pick this for a payoff timeline across credit cards under one strategy, including windfalls or fixed payments; pick compare_payoff_strategies to compare avalanche against snowball, calculate_loan_payoff for a single installment loan, and analyze_cash_advance when one card's cash advance is the question. Supports dual APR segments (purchase + cash advance), two strategies (avalanche, snowball), fixed/dynamic payment modes, and an optional schedule of one-time windfalls (tax refund, bonus, inheritance) applied at specific months.

Returns:

  • debt-free date, total interest, per-card payoff order

  • first_month_interest_ratio_frac (the first month's interest/payment fraction) and lifetime_interest_ratio_frac (whole-projection total_interest/total_paid fraction)

  • domain warnings emitted as warnings[].type (DEATH_SPIRAL, CA_TRAP, MIN_PAYMENT_TRAP, DAY_ZERO_COST, DEBT_GROWING, PROMO_CLIFF, PREDATORY_RATE, SPEND_EXCEEDS_PAYDOWN, PLAN_TERM_ELAPSED_WITH_BALANCE, NO_PAYOFF_WITHIN_PROJECTION), and, separately, validation disclosures emitted as warnings[].code (e.g. MINIMUM_PAYMENT_NOT_BINDING on the default dynamic-budget path, MINIMUM_PAYMENT_NOT_BINDING_IN_BASELINE when fixed_payments = true; these two shapes coexist in the same warnings[] array but are distinguishable by which keys each entry carries)

  • when a plan exists (extra_monthly_payment > 0, windfalls present, or fixed_payments = true), a min_payments_only baseline block with interest_saved_vs_min_payments and months_saved_vs_min_payments, the interest and time difference versus paying minimums only; when windfalls are also present the response additionally gains a baseline vs with_windfalls savings block with months_saved_vs_baseline and interest_saved_vs_baseline

The response echoes strategy (canonical lowercase 'avalanche' | 'snowball', the strategy the plan was actually simulated under) at the top level.

The response includes chart_hints with rendering directives any client can use.

Top-level entered_starting_balance echoes the caller's supplied balances by segment type (total, purchase, cash_advance, balance_transfer, promotional, installment_plan), before any fee, recurring spend, or interest posts; total is the sum of the other five. Top-level total_purchase_segment_fees_paid reports the portion of card fees (plan_fees_monthly plus annual_fee accruals) charged to the purchase segment across the projection, also present on every monthly_totals[] and analysis.buckets[] row.

These month counts are each a {status, value, explanation} object rather than a bare month count: months_to_payoff, card_payoff_order[].payoff_month, min_payments_only.months, minimums_only_baseline.months, and, when windfalls are supplied, baseline.months and with_windfalls.months. status is "defined" with an integer value when that plan reaches a zero balance, and "none" with value null when it does not, in which case explanation says why. The four savings fields (interest_saved_vs_min_payments, months_saved_vs_min_payments, months_saved_vs_baseline, interest_saved_vs_baseline) are {status, value, explanation} objects too: "defined" with the difference only when both compared runs reach a zero balance, and "none" with value null otherwise, in which case explanation names each run that does not.

Input Schema

NameRequiredDescriptionDefault
cardsYesCredit cards to pay off, 1 to 20. Required: omitting the field answers contract_error MISSING_REQUIRED_FIELD, while an explicit JSON null answers parse_error ("cards is required and must be an array."); the two are not the same rejection. Every card may carry segments[]. Max 100 segments total across all cards in the request. Per-card, per-type caps: 1 purchase, 1 cash_advance, 5 balance_transfer, 1 promotional, 10 installment_plan.
outputNoValid values: 'summary' (default), 'inline'. summary returns a compact response with a data_preview block; inline returns the full payload.
strategyNoPayoff order: 'avalanche' pays the highest APR first, 'snowball' the smallest balance first. Optional; defaults to 'avalanche' when omitted. Matched case-insensitively, and the response echoes the canonical lowercase form actually simulated.
windfallsNoOne-time principal payments, at most 12, e.g. a tax refund or a year-end bonus. Optional; a JSON null is treated as omitted. When non-empty the response gains baseline, with_windfalls, months_saved_vs_baseline, interest_saved_vs_baseline, windfalls_applied[] and windfalls_unused[]; an empty array returns the same shape as omitting it.
chart_titleNoOverride for the chart title. Optional; must not contain an em dash or en dash. Max 120 characters.
per_segmentNoWhether each schedule row carries its per-segment breakdown. Optional; defaults to true when omitted.
full_scheduleNoWhether to return the full per-card month-by-month schedule. Optional; defaults to false when omitted. The per-card monthly_schedule is capped at 1000 rows total across cards and months, so later months are omitted on a long multi-card payoff; the portfolio-level monthly_totals series is always complete and is the one to read for the whole timeline.
apply_rate_capNoWhether to cap each card's APR at the regulatory ceiling before simulating. Optional; defaults to false when omitted.
fixed_paymentsNoWhether to hold each card's payment constant for the whole payoff. Optional; defaults to false when omitted. When true, each card's payment is locked at max(cards[].minimum_payment, that card's issuer minimum computed at month 1), and the total monthly budget (those locked amounts plus extra_monthly_payment) is held constant for the whole payoff, so once a card is paid off its freed-up payment rolls forward onto the remaining cards. This is the ONLY mode in which cards[].minimum_payment affects calculate_cc_payoff's result; compare_payoff_strategies has no fixed_payments parameter and binds minimum_payment on both of its strategy arms wherever it is higher than that card's month-1 issuer minimum.
extra_monthly_paymentNoExtra monthly payment in dollars, $0 to $1,000,000,000, applied on top of the required minimums. Optional; defaults to 0 when omitted.
include_card_timelineNoWhether to add the card_timeline block, a per-card view of when each card clears. Optional; defaults to false when omitted.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed2 schema fields changed
    • changedInput schema / properties / cards / items / properties / minimum_payment / description
      Previous value: -"Minimum payment in dollars, $0 to $1,000,000,000 (optional, omit or set 0 to auto-calculate). Where it binds, the card is locked at max(minimum_payment, that card's issuer minimum computed at month 1), and that locked amount is paid every month. On calculate_cc_payoff it binds ONLY when fixed_payments = true; on the DEFAULT path (fixed_payments = false) it is NOT used, because the plan recomputes each card's issuer minimum from its current balance every month, and the response discloses that as MINIMUM_PAYMENT_NOT_BINDING. On compare_payoff_strategies there is no fixed_payments parameter and supplying one is rejected: the avalanche and snowball arms ALWAYS run locked, so the value DOES bind and moves both arms, while the minimum-payments-only baseline always recomputes and ignores it, disclosed as MINIMUM_PAYMENT_NOT_BINDING_IN_BASELINE."New value: +"Minimum payment in dollars, $0 to $1,000,000,000 (optional, omit or set 0 to auto-calculate). Where it binds, the card is locked at max(minimum_payment, that card's issuer minimum computed at month 1), and that locked amount is paid every month. On calculate_cc_payoff it binds ONLY when fixed_payments = true; on the DEFAULT path (fixed_payments = false) it is NOT used, because the plan recomputes each card's issuer minimum from its current balance every month, and the response discloses that as MINIMUM_PAYMENT_NOT_BINDING. On compare_payoff_strategies there is no fixed_payments parameter and supplying one is rejected: the avalanche and snowball arms ALWAYS run locked, so the value binds in both arms wherever it is higher than that card's month-1 issuer minimum, while the minimum-payments-only baseline always recomputes and ignores it, disclosed as MINIMUM_PAYMENT_NOT_BINDING_IN_BASELINE."
    • changedInput schema / properties / fixed_payments / description
      Previous value: -"Whether to hold each card's payment constant for the whole payoff. Optional; defaults to false when omitted. When true, each card's payment is locked at max(cards[].minimum_payment, that card's issuer minimum computed at month 1), and the total monthly budget (those locked amounts plus extra_monthly_payment) is held constant for the whole payoff, so once a card is paid off its freed-up payment rolls forward onto the remaining cards. This is the ONLY mode in which cards[].minimum_payment affects calculate_cc_payoff's result; compare_payoff_strategies has no fixed_payments parameter and binds minimum_payment on both of its strategy arms regardless."New value: +"Whether to hold each card's payment constant for the whole payoff. Optional; defaults to false when omitted. When true, each card's payment is locked at max(cards[].minimum_payment, that card's issuer minimum computed at month 1), and the total monthly budget (those locked amounts plus extra_monthly_payment) is held constant for the whole payoff, so once a card is paid off its freed-up payment rolls forward onto the remaining cards. This is the ONLY mode in which cards[].minimum_payment affects calculate_cc_payoff's result; compare_payoff_strategies has no fixed_payments parameter and binds minimum_payment on both of its strategy arms wherever it is higher than that card's month-1 issuer minimum."
  2. Changed5 schema fields changed
    • changedInput schema / properties / cards / items / properties / cash_advance_apr_pct / description
      Previous value: -"Cash advance APR as a percentage, greater than 0 and up to 100. Required when cash_advance_balance is greater than 0: cash advances have no promotional 0% product, so a 0% rate on a real advance balance is a data-entry error."New value: +"Cash advance APR as a percentage, greater than 0 and up to 100. Required when cash_advance_balance is greater than 0. These flat fields have no promo input, so 0 is rejected: nothing would say when a 0% rate ends. For a promotional cash advance, such as a 0% direct-deposit or check offer, use segments[] with a cash_advance segment carrying promo_expires_month and revert_apr_pct."
    • changedInput schema / properties / cards / items / properties / segments / items / properties / apr_pct / description
      Previous value: -"Annual percentage rate for this segment, 0 to 100, e.g. 24.99 not 0.2499. Required when balance is greater than 0 on purchase, cash_advance, balance_transfer and promotional. On cash_advance it must be greater than 0 whenever a balance is carried. Must be 0 on installment_plan, which uses monthly_fee instead, so omitting it there is the correct shape."New value: +"Annual percentage rate for this segment, 0 to 100, e.g. 24.99 not 0.2499. Required when balance is greater than 0 on purchase, cash_advance, balance_transfer and promotional. On cash_advance with a balance it must be supplied and greater than 0, unless the segment also carries a usable promo_expires_month and revert_apr_pct: then it is the promotional rate and may be 0. Must be 0 on installment_plan, which uses monthly_fee instead, so omitting it there is the correct shape."
    • changedInput schema / properties / cards / items / properties / segments / items / properties / promo_expires_month / description
      Previous value: -"Month at which apr_pct flips to revert_apr_pct, 1 to 480. Required on balance_transfer and promotional. Ignored on purchase, cash_advance and installment_plan."New value: +"Last month charged at apr_pct, 1 to 480; revert_apr_pct applies from the next month. Month 1 is the first statement cycle after the statement the balance comes from. From a statement, count the closing dates after it, up to and including the last one on or before the promo end date; a cycle the end date splits counts at revert_apr_pct. If no closing date qualifies, the promo ends inside month 1: enter the post-promo rate as both apr_pct and revert_apr_pct, with promo_expires_month 1. Required on balance_transfer and promotional. On cash_advance, supply it with revert_apr_pct, under the rule stated on revert_apr_pct. Ignored on purchase and installment_plan."
    • changedInput schema / properties / cards / items / properties / segments / items / properties / revert_apr_pct / description
      Previous value: -"Rate this segment reverts to once its promo expires, 0 to 100, and at least apr_pct. Required on balance_transfer and promotional. Ignored on purchase and cash_advance. Rejected on installment_plan with FIELD_NOT_ALLOWED_HERE: an expired plan's residual keeps its existing rate rather than repricing."New value: +"Rate this segment reverts to once its promo expires, 0 to 100, and at least apr_pct: the post-promo APR the offer disclosed, wherever the offer or the statement prints it. Only when neither prints one for this balance, take the regular rate of this balance's category, not the promo rate. A direct-deposit or check cash advance reverts to the cash-advance APR for those advances, which can differ from the bank cash-advance APR and is not the balance-transfer APR. Required on balance_transfer and promotional. On cash_advance, supply it with promo_expires_month: alone or invalid, the pair is not used and an INPUT_NOT_USED warning says why, except at an apr_pct of 0 with a balance, where the request is rejected. Ignored on purchase. Rejected on installment_plan with FIELD_NOT_ALLOWED_HERE: an expired plan's residual keeps its existing rate rather than repricing."
    • changedInput schema / properties / cards / items / properties / segments / items / properties / type / description
      Previous value: -"Segment type. REQUIRED on every segment: omitting it is rejected. Per-type field matrix. Every field below is classified for every segment type: REQUIRED (omitting it is rejected), OPTIONAL (accepted either way and read), IGNORED (accepted and never read for this type, though a value can still fail the field's own range check), or REJECTED (supplying a disallowed value is an error; the field's own rule below says exactly which values are disallowed). Value rules are stated separately from applicability, because a field can be accepted on a type and still be restricted there. The validators are the authority; this matrix describes them.\n- type: REQUIRED on every type. Values: one of purchase, cash_advance, balance_transfer, promotional, installment_plan.\n- balance: REQUIRED on every type. Values: $0 to $1,000,000,000; above $50,000 returns a HIGH_CARD_BALANCE warning, not an error.\n- apr_pct: REQUIRED on purchase, cash_advance, balance_transfer, promotional; OPTIONAL on installment_plan. Values: 0 to 100 as a percentage, e.g. 24.99 not 0.2499. The REQUIRED classification above applies when balance is greater than 0; a zero-balance segment may omit it. Must be greater than 0 on cash_advance carrying a balance. Must equal 0 on installment_plan, where omitting it is the correct shape.\n- revert_apr_pct: REQUIRED on balance_transfer, promotional; IGNORED on purchase, cash_advance; REJECTED on installment_plan. Values: 0 to 100, and at least apr_pct. Rejected on installment_plan with FIELD_NOT_ALLOWED_HERE: an expired plan's residual keeps its existing rate.\n- promo_expires_month: REQUIRED on balance_transfer, promotional; IGNORED on purchase, cash_advance, installment_plan. Values: 1 to 480.\n- monthly_fee: OPTIONAL on installment_plan; REJECTED on purchase, cash_advance, balance_transfer, promotional. Values: $0 or more, and no greater than plan_payment_due, on installment_plan. On every other type a POSITIVE value is rejected with FIELD_NOT_ALLOWED_HERE; 0 and a negative value are accepted and dropped there.\n- plan_payment_due: REQUIRED on installment_plan; REJECTED on purchase, cash_advance, balance_transfer, promotional. Values: greater than $0, at least monthly_fee, and no more than $1,000,000,000, on installment_plan. On every other type a POSITIVE value is rejected with FIELD_NOT_ALLOWED_HERE; 0 and a negative value are accepted and dropped there.\n- remaining_payments: OPTIONAL on installment_plan; IGNORED on purchase, cash_advance, balance_transfer, promotional. Values: 0 or more. 0 means the stated term has ALREADY ended and is valid with any balance. From 1 up, balance must be clearable within the stated term.\n- min_payment_order: OPTIONAL on every type. Values: 1 or more; breaks a tie only between segments sharing both the same current APR and the same installment-plan status.\n- extra_payment_order: OPTIONAL on every type. Values: 0 or more; 0 means skip. Breaks a tie only between segments sharing the exact same current APR.\n- merchant_name: OPTIONAL on installment_plan; IGNORED on purchase, cash_advance, balance_transfer, promotional. Values: 120 characters or fewer.\n- monthly_spend: OPTIONAL on purchase; REJECTED on cash_advance, balance_transfer, promotional, installment_plan. Values: $0 to $50,000 per month. A negative value is rejected as NEGATIVE_BALANCE on every type, purchase included. On every type other than purchase a POSITIVE value is rejected with FIELD_NOT_ALLOWED_HERE; an explicit 0 is accepted.\n- stop_spend_month: OPTIONAL on purchase; IGNORED on cash_advance, balance_transfer, promotional, installment_plan. Values: 0 or more; range checked on every type, read only on purchase."New value: +"Segment type. REQUIRED on every segment: omitting it is rejected. Per-type field matrix. Every field below is classified for every segment type: REQUIRED (omitting it is rejected), OPTIONAL (accepted either way and read), PAIRED (read when supplied together with its partner as a usable pair: promo_expires_month and revert_apr_pct both supplied beside a supplied apr_pct, each within its value rule; a pair that is not usable is rejected when the segment carries a balance and its apr_pct is omitted, 0 or below, and otherwise is not used, with an INPUT_NOT_USED warning; omitting both, or sending both as null, is accepted), IGNORED (accepted and never read for this type, though a value can still fail the field's own range check), or REJECTED (supplying a disallowed value is an error; the field's own rule below says exactly which values are disallowed). Value rules are stated separately from applicability, because a field can be accepted on a type and still be restricted there. The validators are the authority; this matrix describes them.\n- type: REQUIRED on every type. Values: one of purchase, cash_advance, balance_transfer, promotional, installment_plan.\n- balance: REQUIRED on every type. Values: $0 to $1,000,000,000; above $50,000 returns a HIGH_CARD_BALANCE warning, not an error.\n- apr_pct: REQUIRED on purchase, cash_advance, balance_transfer, promotional; OPTIONAL on installment_plan. Values: 0 to 100 as a percentage, e.g. 24.99 not 0.2499. The REQUIRED classification above applies when balance is greater than 0; a zero-balance segment may omit it. Must be greater than 0 on cash_advance carrying a balance, unless that segment carries a usable promo pair (promo_expires_month and revert_apr_pct). Must equal 0 on installment_plan, where omitting it is the correct shape.\n- revert_apr_pct: REQUIRED on balance_transfer, promotional; PAIRED on cash_advance; IGNORED on purchase; REJECTED on installment_plan. Values: 0 to 100, and at least apr_pct. Rejected on installment_plan with FIELD_NOT_ALLOWED_HERE: an expired plan's residual keeps its existing rate.\n- promo_expires_month: REQUIRED on balance_transfer, promotional; PAIRED on cash_advance; IGNORED on purchase, installment_plan. Values: 1 to 480.\n- monthly_fee: OPTIONAL on installment_plan; REJECTED on purchase, cash_advance, balance_transfer, promotional. Values: $0 or more, and no greater than plan_payment_due, on installment_plan. On every other type a POSITIVE value is rejected with FIELD_NOT_ALLOWED_HERE; 0 and a negative value are accepted and dropped there.\n- plan_payment_due: REQUIRED on installment_plan; REJECTED on purchase, cash_advance, balance_transfer, promotional. Values: greater than $0, at least monthly_fee, and no more than $1,000,000,000, on installment_plan. On every other type a POSITIVE value is rejected with FIELD_NOT_ALLOWED_HERE; 0 and a negative value are accepted and dropped there.\n- remaining_payments: OPTIONAL on installment_plan; IGNORED on purchase, cash_advance, balance_transfer, promotional. Values: 0 or more. 0 means the stated term has ALREADY ended and is valid with any balance. From 1 up, balance must be clearable within the stated term.\n- min_payment_order: OPTIONAL on every type. Values: 1 or more; breaks a tie only between segments sharing both the same current APR and the same installment-plan status.\n- extra_payment_order: OPTIONAL on every type. Values: 0 or more; 0 means skip. Breaks a tie only between segments sharing the exact same current APR.\n- merchant_name: OPTIONAL on installment_plan; IGNORED on purchase, cash_advance, balance_transfer, promotional. Values: 120 characters or fewer.\n- monthly_spend: OPTIONAL on purchase; REJECTED on cash_advance, balance_transfer, promotional, installment_plan. Values: $0 to $50,000 per month. A negative value is rejected as NEGATIVE_BALANCE on every type, purchase included. On every type other than purchase a POSITIVE value is rejected with FIELD_NOT_ALLOWED_HERE; an explicit 0 is accepted.\n- stop_spend_month: OPTIONAL on purchase; IGNORED on cash_advance, balance_transfer, promotional, installment_plan. Values: 0 or more; range checked on every type, read only on purchase."
  3. Changed12 schema fields changed
    • changedInput schema / properties / cards / items / properties / minimum_payment / description
      Previous value: -"Minimum payment in dollars, $0 to $1,000,000,000 (optional, omit or set 0 to auto-calculate). Where it binds, the card is locked at max(minimum_payment, that card's issuer minimum computed at month 1), and that locked amount is paid every month. On calculate_cc_payoff it binds ONLY when fixed_payments = true; on the DEFAULT path (fixed_payments = false) it is NOT used, because the plan recomputes each card's issuer minimum from its current balance every month, and the response discloses that as MINIMUM_PAYMENT_NOT_BINDING. On compare_strategies there is no fixed_payments parameter and supplying one is rejected: the avalanche and snowball arms ALWAYS run locked, so the value DOES bind and moves both arms, while the minimum-payments-only baseline always recomputes and ignores it, disclosed as MINIMUM_PAYMENT_NOT_BINDING_IN_BASELINE."New value: +"Minimum payment in dollars, $0 to $1,000,000,000 (optional, omit or set 0 to auto-calculate). Where it binds, the card is locked at max(minimum_payment, that card's issuer minimum computed at month 1), and that locked amount is paid every month. On calculate_cc_payoff it binds ONLY when fixed_payments = true; on the DEFAULT path (fixed_payments = false) it is NOT used, because the plan recomputes each card's issuer minimum from its current balance every month, and the response discloses that as MINIMUM_PAYMENT_NOT_BINDING. On compare_payoff_strategies there is no fixed_payments parameter and supplying one is rejected: the avalanche and snowball arms ALWAYS run locked, so the value DOES bind and moves both arms, while the minimum-payments-only baseline always recomputes and ignores it, disclosed as MINIMUM_PAYMENT_NOT_BINDING_IN_BASELINE."
    • addedInput schema / properties / cards / items / properties / name / maxLength
      Added value: +120
    • addedInput schema / properties / cards / items / properties / purchase_apr_pct / maximum
      Added value: +100
    • addedInput schema / properties / cards / items / properties / purchase_apr_pct / minimum
      Added value: +0
    • addedInput schema / properties / cards / maxItems
      Added value: +20
    • addedInput schema / properties / chart_title / maxLength
      Added value: +120
    • changedInput schema / properties / fixed_payments / description
      Previous value: -"Whether to hold each card's payment constant for the whole payoff. Optional; defaults to false when omitted. When true, each card's payment is locked at max(cards[].minimum_payment, that card's issuer minimum computed at month 1), and the total monthly budget (those locked amounts plus extra_monthly_payment) is held constant for the whole payoff, so once a card is paid off its freed-up payment rolls forward onto the remaining cards. This is the ONLY mode in which cards[].minimum_payment affects calculate_cc_payoff's result; compare_strategies has no fixed_payments parameter and binds minimum_payment on both of its strategy arms regardless."New value: +"Whether to hold each card's payment constant for the whole payoff. Optional; defaults to false when omitted. When true, each card's payment is locked at max(cards[].minimum_payment, that card's issuer minimum computed at month 1), and the total monthly budget (those locked amounts plus extra_monthly_payment) is held constant for the whole payoff, so once a card is paid off its freed-up payment rolls forward onto the remaining cards. This is the ONLY mode in which cards[].minimum_payment affects calculate_cc_payoff's result; compare_payoff_strategies has no fixed_payments parameter and binds minimum_payment on both of its strategy arms regardless."
    • changedInput schema / properties / output / enum
      Previous value: -[
      -  "summary",
      -  "inline"
      -]New value: +[
      +  "summary",
      +  "inline",
      +  null
      +]
    • changedInput schema / properties / output / type
      Previous value: -"string"New value: +[
      +  "string",
      +  "null"
      +]
    • addedInput schema / properties / strategy / enum
      Added value: +[
      +  "avalanche",
      +  "snowball",
      +  null
      +]
    • addedInput schema / properties / windfalls / items / properties / label / maxLength
      Added value: +120
    • addedInput schema / properties / windfalls / maxItems
      Added value: +12
  4. Changed4 schema fields changed
    • removedInput schema / properties / output / default
      Removed value: -null
    • changedInput schema / properties / output / description
      Previous value: -"Response verbosity: 'summary' (default) | 'inline' | 'capture'. summary returns a compact response with a data_preview block; inline returns the full payload; capture writes the full payload to this server's local disk for the chart-render pipeline and returns a capture_ref URI. capture is available on the local stdio transport only, and the hosted HTTP transport rejects it with a structured error naming 'summary' and 'inline' as the valid alternatives, since a capture_ref would be a dead link for a remote caller. Optional; defaults to 'summary' when omitted."New value: +"Valid values: 'summary' (default), 'inline'. summary returns a compact response with a data_preview block; inline returns the full payload."
    • addedInput schema / properties / output / enum
      Added value: +[
      +  "summary",
      +  "inline"
      +]
    • changedInput schema / properties / output / type
      Previous value: -[
      -  "string",
      -  "null"
      -]New value: +"string"
  5. Changed13 schema fields changed
    • addedInput schema / properties / apply_rate_cap
      Added value: +{
      +  "default": null,
      +  "description": "Whether to cap each card's APR at the regulatory ceiling before simulating. Optional; defaults to false when omitted.",
      +  "type": [
      +    "boolean",
      +    "null"
      +  ]
      +}
    • addedInput schema / properties / cards
      Added value: +{
      +  "description": "Credit cards to pay off, 1 to 20. Required: omitting the field answers contract_error MISSING_REQUIRED_FIELD, while an explicit JSON null answers parse_error (\"cards is required and must be an array.\"); the two are not the same rejection. Every card may carry segments[]. Max 100 segments total across all cards in the request. Per-card, per-type caps: 1 purchase, 1 cash_advance, 5 balance_transfer, 1 promotional, 10 installment_plan.",
      +  "items": {
      +    "properties": {
      +      "annual_fee": {
      +        "description": "Annual fee in dollars, $0 to $1,000,000,000. Optional; defaults to 0. Charged at month 1 and every twelfth month after, while the card carries a balance.",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "cash_advance_apr_pct": {
      +        "description": "Cash advance APR as a percentage, greater than 0 and up to 100. Required when cash_advance_balance is greater than 0: cash advances have no promotional 0% product, so a 0% rate on a real advance balance is a data-entry error.",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "cash_advance_balance": {
      +        "description": "Cash advance balance in dollars, $0 to $1,000,000,000. Optional; defaults to 0 when omitted. A POSITIVE value alongside a non-empty segments[] is rejected, the same way purchase_balance is. An explicit 0 is accepted.",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "minimum_payment": {
      +        "description": "Minimum payment in dollars, $0 to $1,000,000,000 (optional, omit or set 0 to auto-calculate). Where it binds, the card is locked at max(minimum_payment, that card's issuer minimum computed at month 1), and that locked amount is paid every month. On calculate_cc_payoff it binds ONLY when fixed_payments = true; on the DEFAULT path (fixed_payments = false) it is NOT used, because the plan recomputes each card's issuer minimum from its current balance every month, and the response discloses that as MINIMUM_PAYMENT_NOT_BINDING. On compare_strategies there is no fixed_payments parameter and supplying one is rejected: the avalanche and snowball arms ALWAYS run locked, so the value DOES bind and moves both arms, while the minimum-payments-only baseline always recomputes and ignores it, disclosed as MINIMUM_PAYMENT_NOT_BINDING_IN_BASELINE.",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "monthly_spend": {
      +        "description": "Recurring monthly purchase amount charged to this card's purchase balance before interest accrues, $0 to $50,000 per month. Optional; defaults to 0. Use the segment-level monthly_spend instead when supplying segments[].",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "name": {
      +        "description": "Card label, e.g. 'Chase Sapphire', 120 characters or fewer. Optional; a card without a name is auto-numbered ('Card 1', 'Card 2', and so on). Echoed into per-card results and warning text. A name over the cap is replaced in error field paths by a key built from its zero-based card index, written as '#0 (name over cap)', '#1 (name over cap)' and so on.",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "plan_fees_monthly": {
      +        "description": "Recurring monthly card fee in dollars, $0 to $1,000,000,000, e.g. a pay-over-time plan fee. Optional; defaults to 0. Charged every month while the card carries a balance.",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "purchase_apr_pct": {
      +        "description": "Purchase APR as a percentage, 0 to 100, e.g. 24.99 not 0.2499. Required when purchase_balance is greater than 0: an omitted APR would silently compute $0 interest.",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "purchase_balance": {
      +        "description": "Purchase balance in dollars, $0 to $1,000,000,000. Required unless segments[] carries at least one segment: an omitted purchase_balance would silently report a $0, 0-month payoff. Supplying a POSITIVE value alongside a non-empty segments[] is rejected, since the two would be competing sources for the same balance. An explicit 0 is accepted.",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "segments": {
      +        "description": "Per-segment breakdown of this card's balances. Optional; when it carries at least one segment it SUPERSEDES purchase_balance and cash_advance_balance, and a POSITIVE value in either of those is rejected. An empty segments[] is treated as absent, so purchase_balance is then required. At most 100 segments across the whole request. Per card, per type: 1 purchase, 1 cash_advance, 5 balance_transfer, 1 promotional, 10 installment_plan. Each item carries its own type, balance, rate and payment priority; see the type field on the item for the per-type field matrix.",
      +        "items": {
      +          "properties": {
      +            "apr_pct": {
      +              "default": null,
      +              "description": "Annual percentage rate for this segment, 0 to 100, e.g. 24.99 not 0.2499. Required when balance is greater than 0 on purchase, cash_advance, balance_transfer and promotional. On cash_advance it must be greater than 0 whenever a balance is carried. Must be 0 on installment_plan, which uses monthly_fee instead, so omitting it there is the correct shape.",
      +              "type": [
      +                "number",
      +                "null"
      +              ]
      +            },
      +            "balance": {
      +              "description": "Segment balance in dollars, $0 to $1,000,000,000. REQUIRED on every segment type: an omitted balance would silently report a $0, 0-month payoff, so it is rejected instead. An explicit 0 is valid, for example a paid-off balance_transfer segment kept for schedule continuity.",
      +              "type": "number"
      +            },
      +            "extra_payment_order": {
      +              "default": null,
      +              "description": "Extra-payment tie-break priority, 0 or more, where 0 means this segment never receives extra payment. Optional on every type, and the default for installment_plan. Only breaks a tie between segments sharing the exact same current APR, since the Credit CARD Act (TILA §164(b), 15 U.S.C. §1666c(b)) / 12 CFR 1026.53(a) always pays the highest-current-APR segment first regardless of this value.",
      +              "type": [
      +                "integer",
      +                "null"
      +              ]
      +            },
      +            "merchant_name": {
      +              "default": null,
      +              "description": "Label for an installment plan's merchant, 120 characters or fewer. Optional on installment_plan, ignored on every other type.",
      +              "type": [
      +                "string",
      +                "null"
      +              ]
      +            },
      +            "min_payment_order": {
      +              "default": null,
      +              "description": "Minimum-payment tie-break priority, 1 or more. Optional on every type. The minimum first pays any installment_plan's locked plan_payment_due in full, then the lowest-current-APR segment; this value only breaks a tie between segments sharing both the same current APR and the same installment-plan status.",
      +              "type": [
      +                "integer",
      +                "null"
      +              ]
      +            },
      +            "monthly_fee": {
      +              "default": null,
      +              "description": "Fixed fee charged each month while an installment plan's term is active, $0 or more and no greater than plan_payment_due. Optional on installment_plan. On every other type a positive value is rejected with FIELD_NOT_ALLOWED_HERE, while 0 and a negative value are accepted and dropped.",
      +              "type": [
      +                "number",
      +                "null"
      +              ]
      +            },
      +            "monthly_spend": {
      +              "default": null,
      +              "description": "Recurring monthly purchase amount posted to this segment before interest accrues, $0 to $50,000 per month. Optional on purchase. A negative value is rejected as NEGATIVE_BALANCE on every type. On every type other than purchase a positive value is rejected with FIELD_NOT_ALLOWED_HERE, since those balances do not accept new purchases; an explicit 0 is accepted.",
      +              "type": [
      +                "number",
      +                "null"
      +              ]
      +            },
      +            "plan_payment_due": {
      +              "default": null,
      +              "description": "Locked monthly payment for an installment plan, principal plus monthly_fee. Required on installment_plan, greater than $0, at least monthly_fee, and no more than $1,000,000,000. On every other type a positive value is rejected with FIELD_NOT_ALLOWED_HERE, while 0 and a negative value are accepted and dropped.",
      +              "type": [
      +                "number",
      +                "null"
      +              ]
      +            },
      +            "promo_expires_month": {
      +              "default": null,
      +              "description": "Month at which apr_pct flips to revert_apr_pct, 1 to 480. Required on balance_transfer and promotional. Ignored on purchase, cash_advance and installment_plan.",
      +              "type": [
      +                "integer",
      +                "null"
      +              ]
      +            },
      +            "remaining_payments": {
      +              "default": null,
      +              "description": "Payments left in an installment plan's stated term, 0 or more. Optional on installment_plan, ignored on every other type. 0 means the term has ALREADY ended and is valid with any balance, including a positive residual. From 1 up, the balance must be clearable within the stated term and must not be clearable a month early.",
      +              "type": [
      +                "integer",
      +                "null"
      +              ]
      +            },
      +            "revert_apr_pct": {
      +              "default": null,
      +              "description": "Rate this segment reverts to once its promo expires, 0 to 100, and at least apr_pct. Required on balance_transfer and promotional. Ignored on purchase and cash_advance. Rejected on installment_plan with FIELD_NOT_ALLOWED_HERE: an expired plan's residual keeps its existing rate rather than repricing.",
      +              "type": [
      +                "number",
      +                "null"
      +              ]
      +            },
      +            "stop_spend_month": {
      +              "default": null,
      +              "description": "Month index at which recurring spend stops, 0 or more. Read on purchase only, and range checked on every type. Omit to spend for the whole projection.",
      +              "type": [
      +                "integer",
      +                "null"
      +              ]
      +            },
      +            "type": {
      +              "description": "Segment type. REQUIRED on every segment: omitting it is rejected. Per-type field matrix. Every field below is classified for every segment type: REQUIRED (omitting it is rejected), OPTIONAL (accepted either way and read), IGNORED (accepted and never read for this type, though a value can still fail the field's own range check), or REJECTED (supplying a disallowed value is an error; the field's own rule below says exactly which values are disallowed). Value rules are stated separately from applicability, because a field can be accepted on a type and still be restricted there. The validators are the authority; this matrix describes them.\n- type: REQUIRED on every type. Values: one of purchase, cash_advance, balance_transfer, promotional, installment_plan.\n- balance: REQUIRED on every type. Values: $0 to $1,000,000,000; above $50,000 returns a HIGH_CARD_BALANCE warning, not an error.\n- apr_pct: REQUIRED on purchase, cash_advance, balance_transfer, promotional; OPTIONAL on installment_plan. Values: 0 to 100 as a percentage, e.g. 24.99 not 0.2499. The REQUIRED classification above applies when balance is greater than 0; a zero-balance segment may omit it. Must be greater than 0 on cash_advance carrying a balance. Must equal 0 on installment_plan, where omitting it is the correct shape.\n- revert_apr_pct: REQUIRED on balance_transfer, promotional; IGNORED on purchase, cash_advance; REJECTED on installment_plan. Values: 0 to 100, and at least apr_pct. Rejected on installment_plan with FIELD_NOT_ALLOWED_HERE: an expired plan's residual keeps its existing rate.\n- promo_expires_month: REQUIRED on balance_transfer, promotional; IGNORED on purchase, cash_advance, installment_plan. Values: 1 to 480.\n- monthly_fee: OPTIONAL on installment_plan; REJECTED on purchase, cash_advance, balance_transfer, promotional. Values: $0 or more, and no greater than plan_payment_due, on installment_plan. On every other type a POSITIVE value is rejected with FIELD_NOT_ALLOWED_HERE; 0 and a negative value are accepted and dropped there.\n- plan_payment_due: REQUIRED on installment_plan; REJECTED on purchase, cash_advance, balance_transfer, promotional. Values: greater than $0, at least monthly_fee, and no more than $1,000,000,000, on installment_plan. On every other type a POSITIVE value is rejected with FIELD_NOT_ALLOWED_HERE; 0 and a negative value are accepted and dropped there.\n- remaining_payments: OPTIONAL on installment_plan; IGNORED on purchase, cash_advance, balance_transfer, promotional. Values: 0 or more. 0 means the stated term has ALREADY ended and is valid with any balance. From 1 up, balance must be clearable within the stated term.\n- min_payment_order: OPTIONAL on every type. Values: 1 or more; breaks a tie only between segments sharing both the same current APR and the same installment-plan status.\n- extra_payment_order: OPTIONAL on every type. Values: 0 or more; 0 means skip. Breaks a tie only between segments sharing the exact same current APR.\n- merchant_name: OPTIONAL on installment_plan; IGNORED on purchase, cash_advance, balance_transfer, promotional. Values: 120 characters or fewer.\n- monthly_spend: OPTIONAL on purchase; REJECTED on cash_advance, balance_transfer, promotional, installment_plan. Values: $0 to $50,000 per month. A negative value is rejected as NEGATIVE_BALANCE on every type, purchase included. On every type other than purchase a POSITIVE value is rejected with FIELD_NOT_ALLOWED_HERE; an explicit 0 is accepted.\n- stop_spend_month: OPTIONAL on purchase; IGNORED on cash_advance, balance_transfer, promotional, installment_plan. Values: 0 or more; range checked on every type, read only on purchase.",
      +              "type": "string"
      +            }
      +          },
      +          "required": [
      +            "type",
      +            "balance"
      +          ],
      +          "type": "object"
      +        },
      +        "type": [
      +          "array",
      +          "null"
      +        ]
      +      },
      +      "stop_spend_month": {
      +        "description": "Month index at which this card's recurring spend stops, 0 or more. Optional; omit to spend for the whole projection.",
      +        "type": [
      +          "integer",
      +          "null"
      +        ]
      +      }
      +    },
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • addedInput schema / properties / chart_title
      Added value: +{
      +  "default": null,
      +  "description": "Override for the chart title. Optional; must not contain an em dash or en dash. Max 120 characters.",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedInput schema / properties / extra_monthly_payment
      Added value: +{
      +  "default": null,
      +  "description": "Extra monthly payment in dollars, $0 to $1,000,000,000, applied on top of the required minimums. Optional; defaults to 0 when omitted.",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • addedInput schema / properties / fixed_payments
      Added value: +{
      +  "default": null,
      +  "description": "Whether to hold each card's payment constant for the whole payoff. Optional; defaults to false when omitted. When true, each card's payment is locked at max(cards[].minimum_payment, that card's issuer minimum computed at month 1), and the total monthly budget (those locked amounts plus extra_monthly_payment) is held constant for the whole payoff, so once a card is paid off its freed-up payment rolls forward onto the remaining cards. This is the ONLY mode in which cards[].minimum_payment affects calculate_cc_payoff's result; compare_strategies has no fixed_payments parameter and binds minimum_payment on both of its strategy arms regardless.",
      +  "type": [
      +    "boolean",
      +    "null"
      +  ]
      +}
    • addedInput schema / properties / full_schedule
      Added value: +{
      +  "default": null,
      +  "description": "Whether to return the full per-card month-by-month schedule. Optional; defaults to false when omitted. The per-card monthly_schedule is capped at 1000 rows total across cards and months, so later months are omitted on a long multi-card payoff; the portfolio-level monthly_totals series is always complete and is the one to read for the whole timeline.",
      +  "type": [
      +    "boolean",
      +    "null"
      +  ]
      +}
    • addedInput schema / properties / include_card_timeline
      Added value: +{
      +  "default": null,
      +  "description": "Whether to add the card_timeline block, a per-card view of when each card clears. Optional; defaults to false when omitted.",
      +  "type": [
      +    "boolean",
      +    "null"
      +  ]
      +}
    • addedInput schema / properties / output
      Added value: +{
      +  "default": null,
      +  "description": "Response verbosity: 'summary' (default) | 'inline' | 'capture'. summary returns a compact response with a data_preview block; inline returns the full payload; capture writes the full payload to this server's local disk for the chart-render pipeline and returns a capture_ref URI. capture is available on the local stdio transport only, and the hosted HTTP transport rejects it with a structured error naming 'summary' and 'inline' as the valid alternatives, since a capture_ref would be a dead link for a remote caller. Optional; defaults to 'summary' when omitted.",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedInput schema / properties / per_segment
      Added value: +{
      +  "default": null,
      +  "description": "Whether each schedule row carries its per-segment breakdown. Optional; defaults to true when omitted.",
      +  "type": [
      +    "boolean",
      +    "null"
      +  ]
      +}
    • addedInput schema / properties / strategy
      Added value: +{
      +  "default": null,
      +  "description": "Payoff order: 'avalanche' pays the highest APR first, 'snowball' the smallest balance first. Optional; defaults to 'avalanche' when omitted. Matched case-insensitively, and the response echoes the canonical lowercase form actually simulated.",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • removedInput schema / properties / toolArguments
      Removed value: -{
      -  "description": "JSON object with these parameters:\n\ncards (REQUIRED array, max 20):\n  - name: string (optional, auto-generated if omitted)\n  - purchase_balance: decimal >= 0 (REQUIRED unless segments[] is supplied)\n  - purchase_apr_pct: decimal 0-100 as percentage, e.g. 24.99 not 0.2499 (REQUIRED unless segments[] is supplied)\n  - cash_advance_balance: decimal >= 0 (optional, default 0)\n  - cash_advance_apr_pct: decimal > 0, <= 100 (required only if cash_advance_balance > 0)\n  - minimum_payment: decimal >= 0 (optional, omit or set 0 to auto-calculate). Binds ONLY when fixed_payments = true: the card is then locked at max(minimum_payment, that card's issuer minimum computed at month 1), and that locked amount is paid every month. On the DEFAULT path (fixed_payments = false), this value is NOT used: the plan recomputes each card's issuer minimum from its current balance every month, so the monthly budget is the sum of those recomputed minimums plus extra_monthly_payment. To hold a stated payment instead, set fixed_payments = true.\n  - annual_fee: decimal >= 0 (optional, default 0). Charged at month 1 and every 12th month thereafter while the card has a balance.\n  - plan_fees_monthly: decimal >= 0 (optional, default 0). Charged every month while the card has a balance (e.g. pay-over-time plan fee).\n  - segments: array (OPTIONAL; when present, supersedes purchase_balance / cash_advance_balance). Max 100 segments total across all cards in the request. Per-card, per-type caps: 1 purchase, 1 cash_advance, 5 balance_transfer, 1 promotional, 10 installment_plan. Each item:\n      - type: 'purchase' | 'cash_advance' | 'balance_transfer' | 'promotional' | 'installment_plan' (REQUIRED). Use 'promotional' for a balance whose 0% (or below-market) intro APR reverts to a standard rate later and is NOT a balance transfer, e.g. a 0% intro purchase APR; it validates and reverts identically to balance_transfer.\n      - balance: decimal >= 0 (REQUIRED)\n      - apr_pct: decimal 0-100 (REQUIRED for purchase / cash_advance / balance_transfer / promotional; must be 0 for installment_plan; for cash_advance, must be > 0 when balance > 0, since cash advances have no promotional 0% product)\n      - revert_apr_pct: decimal 0-100 (REQUIRED for balance_transfer / promotional; APR after promo expires; not applicable to purchase / cash_advance (silently ignored); REJECTED on installment_plan (FIELD_NOT_ALLOWED_HERE) since installment_plan has no post-term-rate concept: an expired plan's residual keeps its existing rate)\n      - promo_expires_month: int >= 1 (REQUIRED for balance_transfer / promotional; month at which apr_pct flips to revert_apr_pct)\n      - monthly_fee: decimal >= 0 (REQUIRED for installment_plan; fixed fee charged each month while the plan's term is active; stops accruing once the term elapses)\n      - plan_payment_due: decimal > 0 (REQUIRED for installment_plan; locked monthly payment, principal + monthly_fee; keeps being charged every month even after the term elapses, until the balance clears)\n      - remaining_payments: int >= 0 (optional, installment_plan only). 0 means the plan's stated term has ALREADY ended: this is the correct, valid way to model an already-expired plan carrying a residual balance, and it is accepted regardless of balance (an expired plan's residual is a real state, not a data-entry error). When remaining_payments >= 1, it must be consistent with balance / plan_payment_due / monthly_fee: net = plan_payment_due - monthly_fee must be positive, or the plan could never amortize (rejected as INSTALLMENT_PLAN_NET_NOT_POSITIVE); balance must be <= remaining_payments * net (the plan must be able to clear within its stated term) and balance must be > (remaining_payments - 1) * net (the term must not be overstated), either violation rejected as INSTALLMENT_PLAN_TERM_MISMATCH. If a plan has already ended, enter remaining_payments as 0 rather than a positive count that cannot clear the balance. An expired plan (remaining_payments reaches 0 with a positive balance still owed, whether supplied that way or reached by simulation) does not freeze and is never repriced: it keeps receiving its locked plan_payment_due every month at its existing rate until the balance clears. One aggregated PLAN_TERM_ELAPSED_WITH_BALANCE warning fires per card (never one per segment), naming every affected plan on that card and its residual, plus the card total.\n      - merchant_name: string (optional, installment_plan only)\n      - min_payment_order: int >= 1 (optional; the minimum first pays any installment_plan segment's locked plan_payment_due in full, structurally, regardless of APR or this value; among the remaining (revolving) segments it then pays down the lowest-current-APR segment first, issuer practice, not statutorily regulated; this value only breaks a tie between segments that share both the exact same current APR AND the same installment-plan status, in which case the higher-balance segment wins; two segments on the same card sharing a value emit a DUPLICATE_PAYMENT_ORDER warning; a supplied value with no APR-tie partner to ever decide emits PAYMENT_ORDER_NOT_DECISIVE)\n      - extra_payment_order: int >= 0 (optional; 0 = SKIP -- this segment never receives extra payment, the default for installment_plan; a positive value only breaks a tie between segments that share the exact same current APR, since the Credit CARD Act (TILA §164(b), 15 U.S.C. §1666c(b)) / 12 CFR §1026.53(a) always pays the highest-current-APR segment first regardless of this value; two segments on the same card sharing a positive value emit a DUPLICATE_PAYMENT_ORDER warning, duplicate 0s never warn; a supplied value with no APR-tie partner to ever decide emits PAYMENT_ORDER_NOT_DECISIVE -- this hits installment_plan hardest, since it is pinned at 0% APR (apr_pct is not read for this segment type), so a positive override on it can win the tie-break only against another segment also at exactly 0%; whenever any positive-APR segment shares the card, the plan stays ELIGIBLE and sorts LAST behind every positive-APR segment, receiving extra only once those higher-APR balances clear)\n    Response gains per-card interest_saving_balance (sum of APR-bearing segments) and installment_balance_locked (sum of installment_plan segments) when segments[] is supplied.\n\nextra_monthly_payment: decimal >= 0 (optional, default 0)\nstrategy: 'avalanche' | 'snowball' (optional, default 'avalanche')\nfixed_payments: bool (optional, default false). When true, each card's payment is locked at max(cards[].minimum_payment, that card's issuer minimum computed at month 1), and the total monthly budget (sum of those locked amounts plus extra_monthly_payment) is held constant for the whole payoff, so once a card is paid off its freed-up payment rolls forward onto the remaining cards. This is the ONLY mode in which cards[].minimum_payment affects the result; see the minimum_payment field above for what happens on the default path.\napply_rate_cap: bool (optional, default false)\nfull_schedule: bool (optional, default false). The per-card monthly_schedule is capped at 1000 rows total (cards x months); on a long multi-card payoff later months are omitted from monthly_schedule. The portfolio-level monthly_totals series is always complete, so read monthly_totals for the full timeline.\nper_segment: bool (optional, default true)\ninclude_card_timeline: bool (optional, default false)\n\nwindfalls: array of one-time principal payments (optional, default empty, max 12 items). Each item:\n  - month: int >= 0 (REQUIRED). 0 means applied before month 1's interest (same as reducing starting principal). N >= 1 applies at the END of calendar month N, so month N+1's interest is calculated on the post-windfall balance.\n  - amount: decimal > 0 (REQUIRED).\n  - label: string (optional). e.g. 'Tax refund', 'Year-end bonus'.\nWhen any windfalls are present the response gains: baseline, with_windfalls, months_saved_vs_baseline, interest_saved_vs_baseline, windfalls_applied[], windfalls_unused[]. Empty windfalls yields the same response shape as before.\n\nMin-payments-only baseline (v1.7, gap #593): when extra_monthly_payment > 0, windfalls are present, or fixed_payments = true, the response also gains:\n  - min_payments_only: { months, interest_cost, amount_paid, payoff_date } - what happens paying minimums only\n  - interest_saved_vs_min_payments: decimal - total interest saved vs paying minimums only (always >= 0)\n  - months_saved_vs_min_payments: int - months saved vs paying minimums only (when minimums never pay off, capped at MaxMonths minus with-plan months)\nTrivial minimums-only calls (no extra, no windfalls, not fixed) keep their current response shape unchanged.\n\nMinimums-only baseline series (v1.9, B2; bucketed v2.5, #1108): under the same plan-exists condition as min_payments_only above, the response also gains minimums_only_baseline: { months, granularity, buckets[], payoff_date } - the minimums-only balance trajectory bucketed through the same shared aggregator as analysis.buckets[] (same bucket row shape: bucket_index, label, plus the 17 money fields), with granularity auto-selected from THIS series' own term, the same standard rule analysis.granularity itself uses. minimums_only_baseline.granularity is NOT guaranteed to equal analysis.granularity: each series auto-selects independently from its own term, so read each series' own granularity rather than assuming they match. Equal bucket-array length is NOT a valid signal that the two granularities match either: a 24-month primary run (monthly, 24 buckets) and a 24-year minimums-only baseline (yearly, 24 buckets) land on the SAME buckets[].length with DIFFERENT granularities. granularity is the only valid mismatch signal; length is not. Computed from the SAME gated simulation as min_payments_only, so months/payoff_date agree between the two blocks. Absent under the identical trivial-call condition as min_payments_only.\n\nchart_title: string (optional) override for the chart title. Must not contain em-dashes or en-dashes. Max 120 characters.\noutput: 'summary' | 'inline' | 'capture' (optional, default 'summary')\n  'capture' is available on the local stdio transport only; the hosted HTTP transport rejects it with a structured error naming 'summary' and 'inline' as the valid alternatives."
      -}
    • addedInput schema / properties / windfalls
      Added value: +{
      +  "default": null,
      +  "description": "One-time principal payments, at most 12, e.g. a tax refund or a year-end bonus. Optional; a JSON null is treated as omitted. When non-empty the response gains baseline, with_windfalls, months_saved_vs_baseline, interest_saved_vs_baseline, windfalls_applied[] and windfalls_unused[]; an empty array returns the same shape as omitting it.",
      +  "items": {
      +    "properties": {
      +      "amount": {
      +        "description": "Dollar amount of the windfall, greater than 0. Required per entry.",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "label": {
      +        "description": "Optional label for this windfall, e.g. 'Tax refund' or 'Year-end bonus'. Max 120 characters.",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "month": {
      +        "description": "Month index the windfall is applied, 0 or more. 0 means applied before month 1's interest; N >= 1 applies at the end of calendar month N. Required per entry.",
      +        "type": [
      +          "integer",
      +          "null"
      +        ]
      +      }
      +    },
      +    "type": [
      +      "object",
      +      "null"
      +    ]
      +  },
      +  "type": [
      +    "array",
      +    "null"
      +  ]
      +}
    • changedInput schema / required
      Previous value: -[
      -  "toolArguments"
      -]New value: +[
      +  "cards"
      +]
  6. First observed

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/destructive/openWorld/idempotent, so the safety profile is covered; the description contributes real behavioral context on top, notably the MINIMUM_PAYMENT_NOT_BINDING disclosure path and the fact that `minimum_payment` only binds when fixed_payments=true. It loses a point because the fixed_payments/minimum_payment explanation largely restates the schema's own parameter text, so some of the added length is not net-new information.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose and sibling routing are front-loaded, but they are preceded by two boilerplate sentences ('Calculation, not advice...') that delay the actual purpose. The return-value section is a dense multi-hundred-word prose block covering warning codes, {status, value, explanation} wrappers, and baseline blocks; the content is needed given no output schema, but it is not structured for scanning and repeats schema-level detail.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, and the description takes on that burden fully: it enumerates the headline results, the warning taxonomy (both warnings[].type and warnings[].code shapes and how to distinguish them), the {status, value, explanation} contract on month counts and savings fields, the min-payments baseline block, and the echo fields. For a complex simulation tool an agent has everything needed to call it and interpret the response.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the parameter descriptions are unusually thorough (per-type field matrix, value rules, rejection codes), so the description's contribution here is thin. It does add a useful routing nuance around segments[] superseding purchase_balance/cash_advance_balance and the windfall/fixed-payment shapes, but the schema already carries the semantics. Baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Calculate credit card payoff timeline with month-by-month amortization') and immediately disambiguates from three siblings by name: `compare_payoff_strategies`, `calculate_loan_payoff`, and `analyze_cash_advance`, each with the condition that selects them. An agent can route correctly without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-pick-this ('a payoff timeline across credit cards under one strategy, including windfalls or fixed payments') and explicit when-to-pick-the-alternative for every adjacent sibling. It also names the feature set that makes this tool the right pick (dual APR segments, two strategies, fixed/dynamic payment modes, windfall schedules), so edge cases have a home.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources