Peach validates migration data at multiple points. Understanding these rules helps prevent failures.
| Rule | Error symptom |
|---|---|
| No gaps between periods | startDate of period N+1 must equal endDate of period N + 1 day |
| No overlapping start-end date ranges | The startDate–endDate range of one period cannot overlap with another period's range |
| No duplicate dates | Two periods cannot share the same startDate, endDate, statementDate, or dueDate |
| Due date falls in next period | Each period's dueDate must fall between the next period's startDate and endDate |
| Rule | Error symptom |
|---|---|
effectiveTimeOfDay must be after 2:00 AM | Transactions with earlier times fail validation |
External transactions require isExternal: true | Live transactions processed by legacy system must be flagged as external |
| Historical transactions use the past-transaction endpoint | Don't use the standard transaction endpoint for pre-cutoff activity |
| Rule | Error symptom |
|---|---|
| All amounts ≥ 0.00 | Negative amounts are rejected |
| Precision of 0.01 | Amounts with more than 2 decimal places are rejected |
Credit balances use reimbursementAmount | Don't use negative principal for credit balances |
| Overdue balances must match obligations | If 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. |
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:
| Month | Peach (actual days) | 30/360 system | Difference per $10,000 at 18% APR |
|---|---|---|---|
| February (non-leap) | 28 days | 30 days | -$0.99 |
| February (leap) | 29 days | 30 days | -$0.49 |
| March | 31 days | 30 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.
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
effectiveDateto 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.
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.
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.
Q: How long does migration take?
Processing time depends on the number of draws, historical activities (purchases, transactions, fees), and batch size.
| Mode | Behavior | Recommended for |
|---|---|---|
Synchronous (sync=true) | Blocks until migration completes or 60-second timeout | Single loans, testing |
| Asynchronous (default) | Returns immediately, processes in background | Production, 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.
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:
- Rolls back the loan status to
pending - Clears the
migrated_attimestamp - Reverses all ledger entries created during the migration attempt
- Resets the migration status to
failed
You can then reset to prepMigration, correct any data issues, and attempt migration again.
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).
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.
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.
- LOC migration procedure — The steps that produce the data validated here.
- After LOC migration — Validate balances and adjust discrepancies.
- LOC migration API and webhooks reference — Endpoints referenced in these errors.