Skip to content

LOC migration validation and troubleshooting

Validation rules and common errors

Peach validates migration data at multiple points. Understanding these rules helps prevent failures.

Period validation

RuleError symptom
No gaps between periodsstartDate of period N+1 must equal endDate of period N + 1 day
No overlapping start-end date rangesThe startDateendDate range of one period cannot overlap with another period's range
No duplicate datesTwo periods cannot share the same startDate, endDate, statementDate, or dueDate
Due date falls in next periodEach period's dueDate must fall between the next period's startDate and endDate

Transaction validation

RuleError symptom
effectiveTimeOfDay must be after 2:00 AMTransactions with earlier times fail validation
External transactions require isExternal: trueLive transactions processed by legacy system must be flagged as external
Historical transactions use the past-transaction endpointDon't use the standard transaction endpoint for pre-cutoff activity

Balance validation

RuleError symptom
All amounts ≥ 0.00Negative amounts are rejected
Precision of 0.01Amounts with more than 2 decimal places are rejected
Credit balances use reimbursementAmountDon't use negative principal for credit balances
Overdue balances must match obligationsIf the obligation states the borrower is overdue by a certain amount, the overdue balance amounts across draws must add up to that amount. Mismatches will cause migration to fail.

Troubleshooting and FAQ

Balance discrepancies

Q: Why doesn't my balance match the legacy system?

Balance discrepancies after migration almost always trace back to one of the following root causes. Work through this diagnostic sequence:

Step 1 — Compare total balance first. If the total outstanding balance (principal + interest + fees) matches but the breakdown doesn't, skip to "Principal/interest split differences" below. If the total doesn't match, continue.

Step 2 — Check balance timing. Balances must be provided as of the migration cutoff date. The codebase enforces a specific rule: balances should include interest accrued through the last day of the previous period (endDate) but should NOT include interest for the statement date (migration period start date). If you included an extra day of interest, you'll see a small positive discrepancy.

Step 3 — Check day count conventions. Peach accrues interest for the actual number of days in each month. Some legacy systems use a 30-day month convention (30/360). This means:

MonthPeach (actual days)30/360 systemDifference per $10,000 at 18% APR
February (non-leap)28 days30 days-$0.99
February (leap)29 days30 days-$0.49
March31 days30 days+$0.49

Over a full year these differences partially cancel out, but at any given cutoff date there may be a discrepancy of a few dollars.

Step 4 — Check rounding behavior. Peach accrues interest daily with high precision, then truncates to the nearest cent at statement date. Fractional cents go to a forgone_interest_rounding bucket. If your legacy system rounds at a different cadence (e.g., per-period rather than per-day), small cumulative rounding differences are expected.

Step 5 — Break down by draw. Compare balances at the draw level. If one specific draw is off, check that draw's interest rate, promo rate, and fee configuration. A mismatched rate will compound over the post-cutoff period.

Minor discrepancies (typically < $1.00) are normal and can be addressed with service credits or adjustment fees. See Step 6: Post-migration → Adjust balances.

Principal/interest split differences

Q: Why is the principal/interest split different from my legacy system?

Peach replays live activity chronologically and applies its own interest calculation rules. Common causes of split differences:

  • Accrual method: Peach uses daily accrual with actual/365 day counting. If your legacy system uses 30/360 or periodic (monthly) accrual, the interest component will differ.
  • Interest truncation at statement date: For LOCs, when non-due interest moves to due at statement date, the system truncates to the nearest cent. The fractional amount goes to forgone_interest_rounding. If your legacy system doesn't truncate at this step, interest will accumulate differently.
  • Grace period retroactive accrual: If a borrower is in grace and makes a payment between statement date and due date, Peach backdates the payment's effectiveDate to the statement date for interest calculation purposes. If your legacy system applies the payment at the actual date, the interest calculation will differ for that period.

The total balance should still match within tolerance — if it doesn't, check the balance timing and day count rules above.

Interest calculation after grace period revocation

Q: Why did the borrower get a large interest charge after migration?

If the borrower was in grace before migration but the grace period was revoked post-migration (because they didn't pay in full by the due date), Peach accrues interest retroactively on the unpaid portion from the statement date, not from the due date. This is by design (see Appendix B: Grace Period for Lines of Credit) but can produce a larger interest charge than expected if the borrower had been in grace for a long period.

Check: Was isGracePeriodEligible set correctly in the migration period data? If it was set to true but the borrower shouldn't have been in grace, the system will have suppressed interest accrual during the migration period and then retroactively accrued when grace was revoked — creating a sudden lump-sum charge.

Loan tapes after migration

Q: How do migrated loans appear on loan tapes?

Peach does not generate loan tapes for periods before migration. After migration, the system generates loan tapes based on your tape configuration. Migrated loans appear on tapes as if they are any other loan. Canceled loans do not appear on loan tapes. Loan tape fields are conditionally enabled based on whether the loan has a migration cutoff — some fields that depend on full loan history may not be available for migrated loans.

Migration processing time

Q: How long does migration take?

Processing time depends on the number of draws, historical activities (purchases, transactions, fees), and batch size.

ModeBehaviorRecommended for
Synchronous (sync=true)Blocks until migration completes or 60-second timeoutSingle loans, testing
Asynchronous (default)Returns immediately, processes in backgroundProduction, batches

If sync mode times out (HTTP 408), the migration is still processing — check migrationStatus via GET /loans/{loanId}. A single loan with a few draws and modest history typically migrates in seconds. Loans with many draws and extensive transaction history may take minutes.

For large batches, always use async mode. Monitor progress by polling loan status or subscribing to the loan.migration.succeeded and loan.migration.failed webhook events.

Failed migration with no errors

Q: Migration status shows failed but I don't see specific errors. What do I do?

Contact Peach Support. Some failure scenarios (e.g., internal timeouts during replay) don't surface user-facing errors. Support can investigate the migration event logs.

When migration fails, the system automatically:

  1. Rolls back the loan status to pending
  2. Clears the migrated_at timestamp
  3. Reverses all ledger entries created during the migration attempt
  4. Resets the migration status to failed

You can then reset to prepMigration, correct any data issues, and attempt migration again.

Re-using external IDs after cancellation

Q: I canceled a migrated loan but can't create a new loan with the same external ID.

External IDs must be unique across all loans in your company, including canceled ones. Use clearAllExternalIds=true when canceling (see Canceling and re-migrating loans), or change the external ID on the incorrectly migrated loan before canceling (e.g., append -canceled).

Sandbox testing

Q: Can I test the full migration workflow in sandbox?

Yes. Sandbox supports the complete migration workflow. Limit testing to 5 loans at a time — larger batches may cause processing delays or aborted migrations.

Migration validation errors

Q: What does "Loan is missing ledger update event" mean?

The POST /migrate endpoint validates that every non-static draw has a corresponding ledger update event (created when you set up the migration period data). This error means you created a draw but didn't create migration period data for it. Create the migration period draw data (POST .../draws/{drawId}/migration/period) for the missing draw, then retry migration.

Q: What does "Operation would lead to periods overlapping" mean?

The period validation checks that no two periods have overlapping start-end date ranges, that there are no gaps between consecutive periods, and that statementDate = endDate + 1 day. Review your past periods data and migration period data for date inconsistencies. Common causes: daylight saving time boundaries, months with different day counts, or off-by-one errors in date calculations.

See also