# Update a Loan Product (V2)

`PUT /lokta-lms/api/v2/loanproducts/{productId}`

Updates a Loan Product through the V2 contract. Preserves V1 partial-update semantics: omitted fields (including loanScheduleType) leave the existing configuration unchanged.

Temporary integration tolerance: unknown JSON request-body fields are ignored — they are not validated, persisted, returned or audited, cause no data change on their own, and must not be relied upon. Known fields remain strictly validated. A future release may restore unsupported-parameter rejection.

## Parameters
- `productId` (path, integer, required): productId

## Request body (application/json)
- `accountMovesOutOfNPAOnlyOnArrearsCompletion` (boolean)
- `accountingRule` (integer)
- `allowApprovedDisbursedAmountsOverApplied` (boolean)
- `allowAttributeOverrides` (AllowAttributeOverrides)
- `allowCompoundingOnEod` (boolean)
- `allowFullTermForTranche` (boolean): Allow full term length for each tranche disbursement. Only available for PROGRESSIVE schedule type with multi-disbursement enabled.
- `allowPartialPeriodInterestCalculation` (boolean)
- `allowVariableInstallments` (boolean)
- `amortizationType` (integer)
- `buyDownExpenseAccountId` (integer)
- `buyDownFeeCalculationType` (string)
- `buyDownFeeIncomeType` (string)
- `buyDownFeeStrategy` (string)
- `buydownfeeClassificationToIncomeAccountMappings` (array of PostClassificationToIncomeAccountMappings)
- `canDefineInstallmentAmount` (boolean)
- `canUseForTopup` (boolean)
- `capitalizedIncomeCalculationType` (string)
- `capitalizedIncomeClassificationToIncomeAccountMappings` (array of PostClassificationToIncomeAccountMappings)
- `capitalizedIncomeStrategy` (string)
- `capitalizedIncomeType` (string)
- `chargeOffBehaviour` (string)
- `chargeOffExpenseAccountId` (integer)
- `chargeOffFraudExpenseAccountId` (integer)
- `chargeOffReasonToExpenseAccountMappings` (array of PostChargeOffReasonToExpenseAccountMappings)
- `charges` (array of LoanProductV2ChargeEntry): Replace-all charge entries; omitting the array preserves associations AND flags; flags omitted on surviving entries are preserved, on new entries default false.
- `closeDate` (string)
- `creditAllocation` (array of CreditAllocationData)
- `currencyCode` (string)
- `dateFormat` (string)
- `daysInMonthType` (integer)
- `daysInYearCustomStrategy` (string)
- `daysInYearType` (integer)
- `deferredFeeIncomeAccountId` (integer)
- `deferredIncomeLiabilityAccountId` (integer)
- `delinquencyBucketId` (integer)
- `description` (string)
- `digitsAfterDecimal` (integer)
- `disallowExpectedDisbursements` (boolean)
- `disallowInterestCalculationOnPastDue` (boolean)
- `disbursedAmountPercentageForDownPayment` (number)
- `dueDaysForRepaymentEvent` (integer)
- `enableAccrualActivityPosting` (boolean)
- `enableAutoRepaymentForDownPayment` (boolean)
- `enableBuyDownFee` (boolean)
- `enableDownPayment` (boolean)
- `enableIncomeCapitalization` (boolean)
- `enableInstallmentLevelDelinquency` (boolean)
- `feeToIncomeAccountMappings` (array of LoanProductChargeToGLAccountMapper)
- `fixedLength` (integer)
- `fixedPrincipalPercentagePerInstallment` (number)
- `fundId` (integer)
- `fundSourceAccountId` (integer)
- `goodwillCreditAccountId` (integer)
- `graceOnArrearsAgeing` (integer)
- `graceOnInterestPayment` (integer)
- `graceOnPrincipalPayment` (integer)
- `holdGuaranteeFunds` (boolean)
- `inArrearsTolerance` (integer)
- `inMultiplesOf` (integer)
- `includeInBorrowerCycle` (boolean)
- `incomeFromBuyDownAccountId` (integer)
- `incomeFromCapitalizationAccountId` (integer)
- `incomeFromChargeOffFeesAccountId` (integer)
- `incomeFromChargeOffInterestAccountId` (integer)
- `incomeFromChargeOffPenaltyAccountId` (integer)
- `incomeFromFeeAccountId` (integer)
- `incomeFromGoodwillCreditFeesAccountId` (integer)
- `incomeFromGoodwillCreditInterestAccountId` (integer)
- `incomeFromGoodwillCreditPenaltyAccountId` (integer)
- `incomeFromPenaltyAccountId` (integer)
- `incomeFromRecoveryAccountId` (integer)
- `installmentAmountInMultiplesOf` (integer)
- `interestCalculationPeriodType` (integer)
- `interestOnLoanAccountId` (integer)
- `interestRateFrequencyType` (integer)
- `interestRatePerPeriod` (number)
- `interestRateVariationsForBorrowerCycle` (array)
- `interestRecalculationCompoundingMethod` (integer)
- `interestRecognitionOnDisbursementDate` (boolean)
- `interestType` (integer)
- `isArrearsBasedOnOriginalSchedule` (boolean)
- `isCompoundingToBePostedAsTransaction` (boolean)
- `isEqualAmortization` (boolean)
- `isInterestRecalculationEnabled` (boolean)
- `isLinkedToFloatingInterestRates` (boolean)
- `loanPortfolioAccountId` (integer)
- `loanScheduleProcessingType` (string)
- `loanScheduleType` (string)
- `locale` (string)
- `maxInterestRatePerPeriod` (number)
- `maxNumberOfRepayments` (integer)
- `maxPrincipal` (number)
- `maxTrancheCount` (integer)
- `merchantBuyDownFee` (boolean)
- `minInterestRatePerPeriod` (number)
- `minNumberOfRepayments` (integer)
- `minPrincipal` (number)
- `minimumDaysBetweenDisbursalAndFirstRepayment` (integer)
- `multiDisburseLoan` (boolean)
- `name` (string)
- `numberOfRepaymentVariationsForBorrowerCycle` (array)
- `numberOfRepayments` (integer)
- `outstandingLoanBalance` (number)
- `overAppliedCalculationType` (string)
- `overAppliedNumber` (integer)
- `overDueDaysForRepaymentEvent` (integer)
- `overdueDaysForNPA` (integer)
- `overpaymentLiabilityAccountId` (integer)
- `paymentAllocation` (array of AdvancedPaymentData)
- `paymentChannelToFundSourceMappings` (array of GetLoanPaymentChannelToFundSourceMappings)
- `penaltyToIncomeAccountMappings` (array of LoanProductChargeToGLAccountMapper)
- `preClosureInterestCalculationStrategy` (integer)
- `principal` (number)
- `principalThresholdForLastInstallment` (integer)
- `principalVariationsForBorrowerCycle` (array)
- `rates` (array of RateData)
- `recalculationCompoundingFrequencyInterval` (integer)
- `recalculationCompoundingFrequencyOnDayType` (integer)
- `recalculationCompoundingFrequencyType` (integer)
- `recalculationRestFrequencyInterval` (integer)
- `recalculationRestFrequencyType` (integer)
- `receivableFeeAccountId` (integer)
- `receivableInterestAccountId` (integer)
- `receivablePenaltyAccountId` (integer)
- `repaymentEvery` (integer)
- `repaymentFrequencyType` (integer)
- `repaymentStartDateType` (integer)
- `rescheduleStrategyMethod` (integer)
- `shortName` (string)
- `startDate` (string)
- `supportedInterestRefundTypes` (array)
- `transactionProcessingStrategyCode` (string)
- `transfersInSuspenseAccountId` (integer)
- `useBorrowerCycle` (boolean)
- `writeOffAccountId` (integer)
- `writeOffReasonsToExpenseMappings` (array of PostWriteOffReasonToExpenseAccountMappings)
- `beneficiaryType` (string): System-defined disbursement payee category; null clears.
- `bpiMethod` (string): BPI treatment; explicit null clears; omitted preserves. Refused for a schedule type that prices no broken period.
- `brokenPeriodDayCount` (string): broken-period day-count convention — same choices as dayCountConvention (parity). Explicit null clears; omitted preserves. Declarative product configuration: stored as sent; the broken-period interest engine consumes it in a later phase.
- `coLendingEligible` (boolean): Co-lending eligibility flag.
- `collectBpiAtDisbursement` (boolean): BPI-at-disbursement flag. Omitted preserves; explicit null is rejected — send false to disable. Refused for a schedule type that prices no broken period, and not yet consumed: no transaction or journal entry nets it from the proceeds.
- `computeAprForKfs` (boolean): APR-for-KFS flag. Omitted preserves; explicit null is rejected — send false to disable. Declarative: stored as sent; the APR/KFS engine consumes it in a later phase.
- `dayCountConvention` (string): stable day-count convention name. Supplying it re-derives and updates the raw axes; explicit null clears ONLY the stored convention (axes untouched); omitted preserves. Updating either raw axis WITHOUT a convention in the same request clears the stored convention (drift prevention). Supplying both requires consistency.
- `defaultRoundingMode` (string): product-level java.math.RoundingMode NAME (never an ordinal or any internal identifier). Null inherits tenant rounding. Declarative: persisted via the shared rounding profile; engine wiring consumes it in a later phase.
- `interestRateStep` (number): fixed nominal rate increment in percentage points per annum. Fixed-rate products only — must be null/omitted for floating products. Null = continuous.
- `maximumDaysBetweenDisbursalAndFirstRepayment` (integer): first-repayment ceiling in days. Explicit null clears; omitted preserves; the combined final state must keep ceiling >= floor.
- `pmtType` (string): payment-formula variant. Omitted preserves; explicit null is rejected — send STANDARD_PMT to reset. PRECISE_PMT is stored as declarative configuration; the precise-PMT engine consumes it in a later phase.
- `principalStep` (number): principal increment anchored at minimumPrincipal. Explicit null clears the step (band returns to CONTINUOUS); omitted preserves. Requires the complete principal band when supplied; the default principal must sit on the grid.
- `productCategoryTags` (array): Replace-all list of stable category-tag codes (code group LoanProductCategory).
- `productType` (string): Stable product-type code (code group LoanProductType); null clears.
- `repayOnDay` (array): replacement pinned-day set (monthly products only; exactly one day active). Explicit null clears the recurrence; omitted preserves. Changing repayment frequency away from monthly requires clearing repayOnDay in the same request.
- `residualEnabled` (boolean): residual (balloon) schedule eligibility flag. Declarative: stored as sent; the residual schedule engine consumes it in a later phase.
- `scheduleSolver` (string): declarative input-mode selection (D-27). Omitted preserves; explicit null CLEARS the value back to "not configured" (nullable, no default). All five values are storable and none changes backend behaviour; changing the solver changes no other product field. The backend never derives Loan terms from scheduleSolver.
- `tenorStep` (integer): increment in NUMBER OF INSTALLMENTS over the repayment-count band (never a calendar duration). Explicit null clears; omitted preserves.

## Responses
- `200`: OK

## Response fields
- `changes` (PutLoanChanges)
- `resourceId` (integer)

## Example request (cURL)
```bash
curl -X PUT \
  'http://localhost:8080/lokta-lms/api/v2/loanproducts/{productId}' \
  -u '{username}:{password}' \
  -H 'Tenant-Identifier: default' \
  -H 'Content-Type: application/json' \
  -d '{
  "accountMovesOutOfNPAOnlyOnArrearsCompletion": false,
  "accountingRule": 3,
  "allowApprovedDisbursedAmountsOverApplied": true,
  "allowAttributeOverrides": {
    "amortizationType": true,
    "graceOnArrearsAgeing": true,
    "graceOnPrincipalAndInterestPayment": true,
    "inArrearsTolerance": true
  },
  "allowCompoundingOnEod": false,
  "allowFullTermForTranche": false,
  "allowPartialPeriodInterestCalculation": true,
  "allowVariableInstallments": false,
  "amortizationType": 1,
  "buyDownExpenseAccountId": 27
}'
```

## Example response
```json
{}
```

Interactive: https://developer.lokta.ai/reference.html#operation/updateLoanProductV2
