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 (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 quantifying 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

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.

Input Schema

TableJSON 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 regardless.
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. 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
  2. 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"
  3. 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"
      +]
  4. First observed

TDQS

A4.8/5.0
Behavior5/5

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

Annotations already declare readOnly/idempotent/non-destructive, and the description adds substantial context beyond them: the 'Calculation, not advice' disclaimer, the domain warnings[] types, the validation disclosures MINIMUM_PAYMENT_NOT_BINDING vs MINIMUM_PAYMENT_NOT_BINDING_IN_BASELINE, the conditional baseline blocks, and the {status, value, explanation} month-count contract. This is rich behavioral disclosure an agent could not infer from the schema.

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

Conciseness4/5

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

Well front-loaded: disclaimer, purpose, sibling routing, then capabilities, then a Returns section. The Returns section is long and enumerates many internal field names and warning codes, but since there is no output schema it is largely justified. Some prose could be trimmed without loss.

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?

No output schema exists, and the description compensates by documenting the return contract in detail (debt-free date, interest ratios, warnings shapes, baseline savings blocks, echo fields, chart_hints, the status-object month counts). For an 11-parameter tool with deep nested inputs, the description is complete enough to call and interpret correctly.

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

Parameters4/5

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

Schema description coverage is 100%, so the baseline is 3; the description adds orientation over the 11 params by tying strategy, payment mode, and windfalls together ('fixed/dynamic payment modes... windfalls applied at specific months') and by noting the strategy echo behavior. It does not add syntax or format detail beyond the exhaustive schema, so it stays short of 5.

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+resource ('Calculate credit card payoff timeline with month-by-month amortization') and immediately distinguishes itself from siblings by name (compare_payoff_strategies, calculate_loan_payoff, analyze_cash_advance). An agent can select this tool 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?

Explicit routing language: 'Pick this for a payoff timeline across credit cards under one strategy... 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.' When-to-use and when-not-to-use are both stated with named alternatives.

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