# 2026 Release Notes

Weekly notes appear below, newest first; the 2026 Q1 quarterly notes follow at the end of the page.

## Week of May 18, 2026

This week reshapes the Cards and Purchases APIs, reorganizes the Docs Hub by domain, and enforces transaction idempotency.

### New Features

#### Card `status` is promoted to a top-level field with a new five-value enum

**Type:** New Feature
**Impact:** Informational
**Area:** Credit Cards & Card Programs

**What:** The Card object's top-level `status` field is now backed by the card status enum with five lender-owned values: `Active`, `Inactive`, `Blocked`, `Closed`, `Expired`. This value is stored separately from the existing issuer-driven internal status (which remains available under `cardDetails.status`). Card list and search endpoints accept the new top-level `status` as a filter parameter.

**Why it matters:** Separates the lender-controlled lifecycle state of a card from the issuer-driven operational state. Previously the two were conflated under a single ten-value enum, making it harder to reason about which transitions you control versus which Marqeta/Galileo/Lithic/Qolo drives.

**How to use it:** Read `status` at the top level for the lender-owned state; read `cardDetails.status` when you need the issuer-driven operational view. Pass `status` as a query parameter on list/search endpoints to filter cards by lender state. See the [Card object reference](/api-docs/api-public#tag/Cards).

#### Top-level `cardId` on Create/Update Purchase, with a `validation.cardIdMissing` warning

**Type:** New Feature
**Impact:** Informational
**Area:** Credit Cards & Card Programs

**What:** Create Purchase and Update Purchase requests now accept an optional top-level `cardId` field, persisted on `loan_purchases` via a new nullable foreign key. When `cardId` is omitted, the response includes a soft `validation.cardIdMissing` warning rather than rejecting the request.

**Why it matters:** Lets you associate a purchase with the specific card used for it at create or update time, instead of inferring the card after the fact. The soft-warning pattern gives clients a deprecation runway to start sending `cardId` before any future hard requirement.

**How to use it:** Include `cardId` (as a `CD-...` public ID) on `POST` and `PATCH` to the Purchase endpoints. Existing flows that omit `cardId` continue to work; if you see `validation.cardIdMissing` in the response warnings, update your purchase-creation flow to include the card. See the [Purchases API reference](/api-docs/api-public#tag/Line-of-Credit-Purchases).

#### Purchase status can transition from `authorized` to `declined`

**Type:** New Feature
**Impact:** Informational
**Area:** Credit Cards & Card Programs

**What:** The Update Purchase endpoint now accepts a status transition from `authorized` to `declined`. Declined purchases are treated like canceled purchases for ledger and credit-utilization purposes — no ledger write, credit hold released.

**Why it matters:** Lets you record a post-authorization decline (for example, a fraud signal arriving after initial authorization) without having to cancel-and-recreate, preserving the authorization audit trail.

**How to use it:** `PATCH` the purchase with `status: "declined"` from an `authorized` state. The transition is also documented in the OpenAPI spec. See the [Purchases API reference](/api-docs/api-public#tag/Line-of-Credit-Purchases).

#### New `company.config.ui.phoneDisclosureNote` field controls Borrower Portal phone-change disclosure

**Type:** New Feature
**Impact:** Informational
**Area:** Borrowers

**What:** A new optional `phoneDisclosureNote` field on `company.config.ui` controls whether a fixed disclosure note is shown in the Borrower Portal when a borrower changes their phone number.

**Why it matters:** Lets you meet jurisdiction-specific phone-disclosure requirements without a code change, by toggling the note per-company.

**How to use it:** Set `company.config.ui.phoneDisclosureNote` via the company configuration endpoint.

### Improvements

#### `Purchase.cardId` is consistently returned across all Purchase read endpoints

**Type:** Improvement
**Impact:** Informational
**Area:** Credit Cards & Card Programs

**What:** Every Purchase read endpoint — single-purchase, list, and embedded responses — now returns `cardId` as a `CD-...` public ID (or `null` if no card is associated). The OpenAPI schema explicitly states this contract, and the change is locked in by a new test suite covering every read endpoint.

**Why it matters:** Lets you rely on `cardId` being present in any Purchase response shape without endpoint-specific conditional logic.

**How to use it:** No action needed — applies automatically. If your integration previously branched on whether `cardId` was present based on which endpoint returned the purchase, that branching can be removed.

#### Request validation improved across several configuration endpoints

**Type:** Improvement
**Impact:** Informational
**Area:** Developer Tools

**What:** Request validation on the employees, loan type create/update, payment instrument create, borrower campaigns, and promo programs endpoints now produces more consistent, field-level error messages. Validation failures return `400` responses with structured error messages identifying the specific fields and constraints that failed.

**Why it matters:** Produces clearer, more actionable error messages when a request to these endpoints contains invalid data.

**How to use it:** No action needed — valid requests continue to work unchanged. If you have integration tests that match on specific error message text from these endpoints, the messages may have shifted shape; the HTTP status codes are unchanged.

#### Docs Hub reorganized into ten domain-first sections

**Type:** Improvement
**Impact:** Informational
**Area:** Developer Tools

**What:** The Peach Docs Hub has been restructured around ten domain sections — Getting Started, Developer Tools, Borrowers, Payments, Loan Lifecycle, Credit Reporting, Communications, Data & Reporting, Servicing & Operations, and Release Notes — replacing the previous twelve product-pillar sections. The homepage has been redesigned with a hero, goal-oriented tiles, and an all-sections list; the top navbar now exposes Docs, Get Started, API Reference, and Release Notes as primary destinations.

**Why it matters:** Content is now organized by what you're trying to do (the domain) rather than what Peach calls the internal product pillar that delivers it, making it easier to find documentation for a task without needing to know Peach's internal product taxonomy.

**How to use it:** Browse from the new homepage or the redesigned top navbar. **If you have bookmarks or internal links pointing to specific Peach documentation pages, expect to update them** — the new structure organizes content by domain rather than the previous product-pillar layout, so URLs for many individual pages have changed. The API reference itself is unchanged.

### Bug Fixes

#### `POST` transactions returns `409 DuplicateApiError` on duplicate `externalId` within the same company

**Type:** Bug Fix
**Impact:** Recommended Action
**Area:** Payments · Developer Tools

Fixed an issue where `POST` transactions accepted duplicate `externalId` values within the same company, silently creating multiple transaction records for what should have been a single idempotent request. `POST` requests submitting a duplicate `externalId` now return `409 DuplicateApiError`. The constraint is enforced for transactions created on or after 2026-05-13, leaving historical duplicates in place — no remediation is needed for past data.

If your transaction-creation integration relies on `externalId` as an idempotency key — the field's intended purpose — treat `409` from this endpoint as a successful no-op (the prior transaction with that `externalId` already exists) rather than as a transient conflict to retry. Submitting the same `externalId` twice was always semantically wrong; the constraint now enforces it explicitly.

#### Preview-mode `POST /unfreeze` no longer emits webhooks for rolled-back loan-status events

**Type:** Bug Fix
**Impact:** Informational
**Area:** Loan Lifecycle · Developer Tools

Fixed an issue where `POST /unfreeze` requests with `previewMode: true` could fire webhooks for `loan_status_changed_event` rows that were rolled back as part of the preview, leaving subscribers receiving webhook payloads whose `eventId` had no matching row in the events table.

#### `POST /api/auth/token` returns `400` when `username` is missing

**Type:** Bug Fix
**Impact:** Informational
**Area:** Developer Tools

Fixed an issue where calling `POST /api/auth/token` without a `username` (or with `username: null`) returned a server error during credential normalization. The endpoint now returns `400 "Must include username"`.

#### Multipart form-data uploads no longer crash in request validation when middleware has pre-read the body

**Type:** Bug Fix
**Impact:** Informational
**Area:** Developer Tools

Fixed an issue where file-only `multipart/form-data` uploads could fail with a server error during request validation when an upstream middleware had pre-read the raw request body. The validator now handles pre-read request bodies correctly for form content types.

#### `GET /companies/{companyId}/users` returns `400` (not `500`) for malformed query strings

**Type:** Bug Fix
**Impact:** Informational
**Area:** Developer Tools

Fixed an issue where `GET /companies/{companyId}/users` requests with a malformed query string — for example `?userType=Agent?page=1`, where `?` was mistakenly used instead of `&` between query parameters — returned a `500 Internal Server Error`. The endpoint now returns `400 Bad Request` for malformed query strings of this shape.

## Week of May 11, 2026

This week brings new Card attributes and credit-agency tooling, plus loan-tape and statement refinements and fixes across statements, payments, and cases.

### New Features

#### Card object adds an `isExternal` attribute

**Type:** New Feature
**Impact:** Informational
**Area:** Credit Cards & Card Programs

**What:** Card objects now include a boolean `isExternal` attribute identifying whether a card was issued outside Peach's card-issuer integrations. The field defaults to `true` on existing cards and on new cards created without explicitly setting it, preserving prior behavior.

**Why it matters:** Lets you distinguish externally-issued cards from cards issued through Peach's Marqeta, Galileo, Lithic, or Qolo integrations when reading Card responses, without inferring from issuer-specific fields.

**How to use it:** Read `isExternal` on any Card response. If you explicitly create a card with `isExternal: false`, the request must include the issuer-specific identifier and token fields that the corresponding card-issuer integration requires; the API now validates these on writes when `isExternal` is false. Contact your Peach implementation team for the issuer-specific identifier and token fields required when setting `isExternal: false`. Existing create/update flows that omit `isExternal` continue to work unchanged. See the [Card object reference](/api-docs/api-public#tag/Cards).

#### New service credit type `settlementOfDebtNoLoss`

**Type:** New Feature
**Impact:** Informational
**Area:** Payments · Credit Reporting

**What:** A new service credit type, `settlementOfDebtNoLoss`, is available on transaction creation and serialization. The type extends the `serviceCreditTypes` and `serviceCreditTypesForCreate` enums in the public OpenAPI spec and is supported by Metro2 reporting via the "AU" special comment.

**Why it matters:** Distinguishes settlement-of-debt transactions that do not result in lender loss from those that do (`settlementOfDebt`), giving you a cleaner audit signal for charge-off reconciliation and credit reporting.

**How to use it:** Pass `creditType: "settlementOfDebtNoLoss"` when creating a service credit transaction. The value flows through to Metro2 reporting and triggers the AU special comment on the next bureau submission. See the [Service Credit Types reference](/api-docs/api-public#tag/Transactions).

#### New endpoint to regenerate SSH keys for credit agencies

**Type:** New Feature
**Impact:** Informational
**Area:** Credit Reporting

**What:** A new `POST /companies/{companyId}/credit-agencies/{creditAgencyId}/regenerate-ssh-keys` endpoint rotates a credit agency's SFTP SSH keypair when `sftpAccessMethod` is `keys`, returning the updated credit agency object with the new key references. The response exposes `sftpSSHPeachPublicKey` and `sftpSSHPeachPrivateKey` as read-only fields.

**Why it matters:** Lets you rotate credit-bureau SFTP credentials on a normal cadence (or in response to a suspected compromise) without involving Peach support, supporting standard key-rotation hygiene for credit reporting integrations.

**How to use it:** `POST` to the endpoint with no body. The response includes the regenerated key references; share the new public key with the receiving bureau through their normal key-update process. Requires the same admin-scoped authorization as other credit-agency endpoints. See the [Credit Agencies API reference](/api-docs/api-public#tag/Credit-Agencies).

#### New endpoints to view and bulk-update dynamic fee type caps per loan type

**Type:** New Feature
**Impact:** Informational
**Area:** Loan Configuration

**What:** Two new endpoints, `GET` and `PATCH /companies/{companyId}/loan-types/{loanTypeId}/dynamic-fee-types/caps`, let you read and bulk-update cap-logic configuration across all dynamic fee types on a loan type in a single request. The `GET` accepts an optional `capType` query parameter to filter results by cap category.

**Why it matters:** Replaces the previous per-fee-type configuration loop with a single batch operation, useful when you're configuring or auditing fee caps across many dynamic fee types on the same loan product.

**How to use it:** `GET` returns the list of dynamic fee types on the loan type with their current `capLogic`; `PATCH` accepts a list of `{ dynamicFeeTypeId, capLogic }` entries to apply in one transaction.

#### Loan tape records use `null` status to indicate in-progress generation

**Type:** New Feature
**Impact:** Informational
**Area:** Data & Reporting

**What:** The `LoanTapeRecord.status` field is now nullable. A freshly-created record returns `status: null` to indicate that generation is in progress; the worker writes the terminal `Succeeded`, `Failed`, or `Created` state when generation completes. Previously the server defaulted new records to `Failed`, which made freshly-triggered jobs look failed while still running.

**Why it matters:** Lets you correctly distinguish "still generating" from "generation failed" when polling for loan tape status, instead of treating an in-progress record as a failed one.

**How to use it:** Handle `status: null` as the in-progress state in your polling logic; treat `Failed` only when the field is explicitly set to that value. The OpenAPI spec documents the null state alongside the existing enum values. See the [Loan Tape Records reference](/api-docs/api-public#tag/Loan-Tapes).

### Improvements

#### `GET /companies/{companyId}/loan-tapes/{loanTapeId}/records` is now documented in the OpenAPI spec

**Type:** Improvement
**Impact:** Informational
**Area:** Data & Reporting · Developer Tools

**What:** The `GET /companies/{companyId}/loan-tapes/{loanTapeId}/records` endpoint is now visible in the rendered API documentation. The endpoint itself has existed and been functional, but was missing the OpenAPI tag that surfaces it in the docs hub. `POST` and `PUT` on the same path remain undocumented because they are worker-internal only.

**Why it matters:** If you've been calling `GET` against this endpoint based on the response shape from other Loan Tape APIs, you can now point at the documented schema directly instead of inferring it.

**How to use it:** No code changes needed. See the newly-rendered [Loan Tape Records reference](/api-docs/api-public#tag/Loan-Tapes).

#### Statement BSTIR: events occurring after a day's interest accrual are lumped into the next local date

**Type:** Improvement
**Impact:** Informational
**Area:** Statements & Compliance

**What:** For loans using the new interest timestamp, non-interest events whose timestamp falls after a day's interest accrual run (such as a backdated transaction recorded at 23:59:59 local time) now shift into the next local date for statement BSTIR aggregation, instead of affecting the accrual day's interest balances. The aggregation also now sorts ledger entry pairs deterministically.

**Why it matters:** Produces more stable statement BSTIR and interest totals when backdated events arrive after the day's accrual has already run, instead of retroactively perturbing accrued interest.

**How to use it:** No action needed — applies automatically to loans on the new interest timestamp. If you reconcile statement BSTIR against your own ledger, the values should now match more closely on days where backdated events were posted late.

#### Canceling autopay no longer cancels same-day scheduled transactions

**Type:** Improvement
**Impact:** Informational
**Area:** Payments

**What:** When you cancel autopay on a loan, scheduled autopay transactions whose `scheduledDate` is today (in the loan type's local timezone) are no longer canceled. The associated `AutopayExpectedPayment` records for those same-day transactions are also preserved. All other scheduled autopay transactions (non-today) continue to be canceled as before.

**Why it matters:** Borrowers who cancel autopay on the same day a payment is scheduled to run still see that already-scheduled payment process, instead of having it disappear mid-day with no replacement.

**How to use it:** No action needed — applies automatically when autopay is canceled. If your application surfaces upcoming autopay transactions to borrowers, expect the same-day transaction to remain present after cancellation.

#### Metro2 "AU" special comment is restricted to specific settlement service credit types

**Type:** Improvement
**Impact:** Informational
**Area:** Credit Reporting

**What:** The Metro2 "Account Paid in Full for Less than Full Balance" (AU) special comment is now triggered only when the clearing service credit is one of `BadDebt`, `SettlementOfDebt`, or `SettlementOfDebtNoLoss`. Previously, a broader set of service credit types could trigger AU, including some that did not represent settlement events.

**Why it matters:** Brings AU reporting into alignment with its intended Metro2 semantics, so charged-off or paid-off zero-balance loans only carry the AU comment when they were actually settled below full balance.

**How to use it:** No action needed — applies automatically to credit reporting output. If you reconcile bureau submissions against expected AU codes, the comment now appears more narrowly than before.

### Bug Fixes

#### `POST` case create returns `200` when `outcome` is provided in the request body

**Type:** Bug Fix
**Impact:** Informational
**Area:** Servicing & Operations

Fixed an issue where passing `outcome` (for example `"noOutcome"`) in a case create request body crashed with `500 Internal Server Error` because the value was not normalized to the expected format. Case create requests with an `outcome` field now return `200 OK` and persist the case correctly.

#### `POST /people/{personId}/cases` returns `400` on `borrowerIds`/`people` mismatch instead of crashing with `500`

**Type:** Bug Fix
**Impact:** Informational
**Area:** Servicing & Operations

Fixed an issue where including `borrowerIds` or `people` in the case create request body — both fields are documented in the OpenAPI spec but were not wired into the handler — caused a `500` server error. The handler now accepts these fields when they match the path borrower (and ignores them), or returns `400 Bad Request` on any mismatch.

#### Loan tape worker no longer crashes on SFTP retry after compression

**Type:** Bug Fix
**Impact:** Informational
**Area:** Data & Reporting

Fixed an issue where, after an SFTP upload failure on a compressed loan tape, the retry attempt failed because cleanup from the prior attempt had already removed the temporary upload data. The retry path now preserves the data needed for subsequent attempts, so compressed-tape retries complete correctly instead of leaving records stuck in `created` status.

#### Twilio per-conversation webhook returns `503` (retryable) when the `Interaction` row is missing

**Type:** Bug Fix
**Impact:** Informational
**Area:** Communications

Fixed an issue where the Twilio per-conversation webhook could return an unhandled error when the associated interaction record was missing. The webhook now logs a warning and returns `503 Service Unavailable`, which Twilio's retry policy handles as a transient failure.

#### Roles permissions endpoint returns `400` when `actions` is missing

**Type:** Bug Fix
**Impact:** Informational
**Area:** Developer Tools

Fixed an issue where a roles permissions request with a missing `actions` field returned a server error instead of a validation error. The endpoint now returns `400 Bad Request` when `actions` is not provided.

#### SCRA chunk processor is idempotent on duplicate file upload

**Type:** Bug Fix
**Impact:** Informational
**Area:** Statements & Compliance

Fixed an issue where re-uploading the same SCRA monitoring file could cause duplicate chunk processing. Duplicate uploads are now handled idempotently, reusing existing artifacts instead of reprocessing.

#### Autopay amount-options no longer flicker hidden during the `defaultType` query race in the Agent Portal

**Type:** Bug Fix
**Impact:** Informational
**Area:** Servicing & Operations

Fixed an issue in the Agent Portal where the autopay amount-options list could briefly disappear when `defaultType` was still loading from a query, leaving the agent with no visible amount choices until the query settled. The options now stay visible during the load and re-evaluate once `defaultType` resolves.

#### `interestAdjustment` service credit type removed from the Issue Service Credit dropdown

**Type:** Bug Fix
**Impact:** Informational
**Area:** Servicing & Operations

Fixed an issue in the Agent Portal where the Issue Service Credit form offered `interestAdjustment` as a selectable service credit type, which the backend does not accept on this flow. The option has been removed from the dropdown; agents previously selecting it would have hit a backend rejection on submit.

#### Six verified discrepancies in the Credit Reporting docs corrected

**Type:** Bug Fix
**Impact:** Recommended Action
**Area:** Credit Reporting

Fixed six documentation discrepancies that integrators following the Credit Reporting docs would have encountered as real implementation bugs: an incorrect HTTP method, URL path, and `deletedReason` enum on the delete-credit-reporting API example; an incorrect mention of bankruptcy in the loss-credit-type list; a missing `rewards` service credit type in the calculations table; an incorrect `effective_date` field name in the Date of Last Payment pseudocode (should be `display_date`); an out-of-order closed-and-zero-balance check in the account-status algorithm; and a `sftpAccessMethod` field documented as required when it is in fact optional with a default. If you have implemented against any of these docs sections, review your code against the corrected versions.

## Week of May 4, 2026

This week adds a public Plaid credentials endpoint, plus Docs Hub llms.txt support and fixes across statements, refunds, and change-due-dates.

### New Features

#### New public `POST /companies/{companyId}/plaid/credentials` endpoint for updating Plaid integration credentials

**Type:** New Feature
**Impact:** Informational
**Area:** Developer Tools · Borrowers

**What:** A new public endpoint, `POST /companies/{companyId}/plaid/credentials`, updates a company's encrypted Plaid integration credentials. The request body requires both `clientId` and `secret`. Previously, Plaid credential updates were only available through internal Peach admin tooling.

**Why it matters:** Lets you rotate or update your Plaid integration credentials directly via the public API as part of normal credential-rotation hygiene, without involving Peach support for each change.

**How to use it:** Send `POST /companies/{companyId}/plaid/credentials` with `{ "clientId": "...", "secret": "..." }` as the request body. The endpoint requires the same admin-scoped authorization as other company-configuration endpoints; non-admin tokens receive `403 Forbidden`. Returns `204 No Content` on success.

### Improvements

#### Docs Hub publishes `llms.txt` for AI assistant navigation; feedback widget and SEO improvements

**Type:** Improvement
**Impact:** Informational
**Area:** Developer Tools

**What:** The Peach Docs Hub now publishes an `llms.txt` file at its root, following the emerging convention for surfacing documentation structure to AI coding assistants (Claude Code, Cursor, Copilot, and others) so they can navigate Peach documentation efficiently when answering integration questions. A feedback widget has also been added to all Docs Hub pages, and SEO indexing has been improved.

**Why it matters:** Engineers using AI coding tools to integrate with Peach can now point those tools at Peach's documentation and get more reliable navigation, reducing the rate at which AI assistants invent endpoint names or misremember field shapes.

**How to use it:** No action needed — applies automatically to anyone accessing the Docs Hub. The `llms.txt` file is at the root of the Docs Hub; the feedback widget appears as a button on every page.

### Bug Fixes

#### LOC statement `total_purchases_for_this_period` reconciles with the summary box when disputed purchases are present

**Type:** Bug Fix
**Impact:** Informational
**Area:** Statements & Compliance · Loan Lifecycle

Fixed an issue where LOC statements containing disputed purchases could produce a summary box where `total_purchases_for_this_period` did not match the sum of individual draws' purchase totals, triggering `summaryBoxMismatch` validation failures in the statement check pipeline.

#### `POST /expected-payments` in preview mode no longer deadlocks against concurrent change-due-dates requests

**Type:** Bug Fix
**Impact:** Informational
**Area:** Loan Lifecycle

Fixed an issue where `POST /expected-payments` (change-due-dates) requests with `previewMode=true` bypassed the reamortization Redis lock, allowing concurrent preview-plus-non-preview or preview-plus-preview requests for the same loan to deadlock via circular Postgres `ShareLock` dependencies on foreign-key constraint checks.

#### Canceling a Refund v2 also cancels its associated service credit transaction

**Type:** Bug Fix
**Impact:** Informational
**Area:** Payments

Fixed an issue where canceling a Refund v2 left its associated service credit transaction (`refund.refund_transaction`) in its previous status, producing inconsistent ledger state where the refund was canceled but the corresponding credit transaction lived on.

## Week of April 27, 2026

This week clarifies the recommended `fields=minimal` value on the loans-list endpoint, adds credit-reporting improvements, and fixes eight bugs across statements, payments, and portals.

### Improvements

#### `fields=minimal` is the recommended value for the `fields` parameter on `GET /loans`

**Type:** Improvement
**Impact:** Informational
**Area:** Developer Tools

**What:** The `fields` query parameter on `GET /loans` now documents `minimal` as the canonical value. The legacy `idsOnly` value continues to work as an alias — the response shape is identical — but `minimal` is the value to use going forward. Note that the internal `GET /companies/{companyId}/loans` endpoint accepts only `minimal` and does not honor the `idsOnly` alias.

**Why it matters:** If you're writing new code against `GET /loans`, use `fields=minimal`. Existing code using `fields=idsOnly` continues to work unchanged.

**How to use it:** Pass `fields=minimal` instead of `fields=idsOnly` on `GET /loans` requests.

#### Credit reporting status preserves historical `reportingEndDate` when it falls in a prior month

**Type:** Improvement
**Impact:** Informational
**Area:** Credit Reporting

**What:** When a stop event is processed against a loan whose `CreditReportingStatus.reportingEndDate` already falls in a previous month (relative to the loan's local current month), Peach now preserves the existing end date instead of overwriting it. The associated `LoanCreditReportingStoppedEvent` is also not re-fired in this case.

**Why it matters:** Avoids retroactively changing historical credit reporting stop dates when a stop event arrives late, keeping bureau-reporting audit trails consistent with what was previously submitted.

**How to use it:** No action needed — applies automatically. If you reconcile credit reporting stop dates against historical bureau submissions, the dates should now remain stable once they pass into a prior month.

### Bug Fixes

#### Statements endpoint rejects external IDs (`ext-` prefix) with `400`

**Type:** Bug Fix
**Impact:** Recommended Action
**Area:** Statements & Compliance

Fixed an issue where the statements endpoint accepted `ext-` prefixed identifiers for `statementPubId` and produced server errors or undefined behavior when the prefix didn't match the expected public ID format. The endpoint now returns `400 Bad Request` for any `statementPubId` value beginning with `ext-`, with an error message indicating that external IDs are not supported on this endpoint.

If your integration has been passing `ext-` prefixed values to the statements endpoint, switch to the corresponding public statement IDs (which can be retrieved from the relevant loan or person resource). The OpenAPI parameter description has been clarified to document the accepted ID format.

#### `GET /events` returns `404` (not `422`) for unresolved external-ID filters

**Type:** Bug Fix
**Impact:** Informational
**Area:** Developer Tools

Fixed an issue where `GET /events` filter values like `personId=ext-someExternalId` were routed through public-ID decoding instead of external-ID resolution, returning `422 InvalidPublicId` even for syntactically valid external identifiers. These requests now resolve external IDs correctly when they exist, and return `404 Not Found` when they don't.

#### Custom payment plan creation returns `400` when `paymentPlanType` is missing

**Type:** Bug Fix
**Impact:** Recommended Action
**Area:** Loan Lifecycle

Fixed an issue where custom payment plan creation requests omitting the `paymentPlanType` field could crash with a server error during plan processing. Requests missing `paymentPlanType` now return `400 Bad Request` with the message "Must specify `paymentPlanType`."

If your custom payment plan flow ever omitted `paymentPlanType` — relying on an implicit default or expecting the server to infer it — update the request to send a valid value before posting.

#### Installment loans activated late no longer produce a first period where `dueDate` equals `startDate`

**Type:** Bug Fix
**Impact:** Informational
**Area:** Loan Lifecycle

Fixed an issue in installment loan schedule generation where loans created from expected payments at origination could produce a first period whose `dueDate` equaled or preceded the loan's `startDate` when activation was delayed, producing a zero-day or negative-length first billing period.

#### Amortization request building correctly sets `originationFeeAprCalcOverride` for predefined loans

**Type:** Bug Fix
**Impact:** Informational
**Area:** Loan Configuration

Fixed an issue in amortization request building where predefined-loan defaulting overwrote `originationFeeChargeLogic` instead of setting `originationFeeAprCalcOverride`, producing incorrect amortization output for loans that relied on predefined-loan defaults during request construction.

#### New transactions are skipped for inactive companies across autopay, scheduled transactions, and ACH updates

**Type:** Bug Fix
**Impact:** Informational
**Area:** Payments

Fixed an issue where autopay scheduling, scheduled-transaction dispatch, and initiated-ACH status updates could continue processing transactions for companies whose status had been set to `inactive`. These workers now check company status, skip processing for inactive companies, and log the skip rather than initiating new transactions.

#### "Peach Support" sidebar link in the Agent Portal opens reliably

**Type:** Bug Fix
**Impact:** Informational
**Area:** Servicing & Operations

Fixed an issue where the "Peach Support" link in the Agent Portal sidebar — which routes through a Zendesk SSO token refetch — could be blocked by browser popup heuristics or land on an invalid URL when the token fetch was slow.

#### Purchase disputes on migrated LOC loans use `originalDrawId` instead of the static migration `drawId`

**Type:** Bug Fix
**Impact:** Informational
**Area:** Loan Lifecycle · Servicing & Operations

Fixed an issue in the Agent Portal where opening or listing purchase disputes for purchases on migrated line-of-credit loans hit `400 Bad Request` because the request used `purchase.drawId` (which points to a static migration draw) rather than `purchase.migration.originalDrawId`.

## Week of April 20, 2026

This week brings configurable per-loan-type fee ordering and fixes across origination and webhooks.

### New Features

#### Configurable fee-bucket ordering per loan type with `paymentWaterfall.feesOrder`

**Type:** New Feature
**Impact:** Informational
**Area:** Loan Configuration

**What:** A new optional `paymentWaterfall.feesOrder` setting on loan types specifies the order in which fee buckets are drawn from during `apply_payments`. Previously, payment application used a fixed fee-iteration order across all loan types.

**Why it matters:** Lets you tune which fee categories are paid down first for a given loan product — for example, prioritizing dispute-related fees over late fees on a credit card product, or vice versa — without code-level changes.

**How to use it:** Set `paymentWaterfall.feesOrder` to an ordered array of unique fee category values when creating or updating a loan type via the Loan Types API. Loan types without an explicit `feesOrder` continue to use the existing default ordering.

### Improvements

#### Autopay enrollment skips the signed-agreement requirement for external payment instruments

**Type:** Improvement
**Impact:** Informational
**Area:** Loan Configuration · Payments

**What:** Autopay enrollment no longer requires `agreementDocumentId` when the selected payment instrument is marked `isExternal: true`. Non-external instruments still require the signed agreement document, and `previewMode` continues to bypass the requirement for both.

**Why it matters:** External payment instruments are processed outside Peach's ACH network and typically have authorization captured by the client through a separate mechanism, so requiring a Peach-stored agreement document for them was duplicative.

**How to use it:** When enrolling autopay with an external instrument, omit `agreementDocumentId`; enrollment will succeed without it. Existing flows for non-external instruments are unchanged.

#### `sftpAchConfirmationFileNameRegex` accepts `null` on payment processors

**Type:** Improvement
**Impact:** Informational
**Area:** Payments

**What:** The `sftpAchConfirmationFileNameRegex` field on payment processor resources is now nullable; clients can explicitly send `null` to indicate no ACH confirmation filename pattern is in use. Previously the field rejected `null` and required a string value.

**Why it matters:** Payment processors that don't generate confirmation files (or that use a fixed filename rather than a pattern) no longer need a placeholder regex.

**How to use it:** Send `sftpAchConfirmationFileNameRegex: null` on `POST` or `PATCH` requests to the payment processor endpoint when no pattern matching is needed. Existing string values continue to work as before.

#### Agent Portal surfaces statements whose PDF is unavailable, marked "PDF missing"

**Type:** Improvement
**Impact:** Informational
**Area:** Statements & Compliance · Servicing & Operations

**What:** The Agent Portal and CRM borrower documents views now display every statement on a loan, including statements whose PDF document hasn't been generated or is otherwise missing. These appear in the list with a "PDF missing" indicator, and download/mail actions are disabled for them. Previously, statements without a `documentDescriptorId` were filtered out of the view entirely. The statements list also now paginates through all historical years rather than capping at the first page.

**Why it matters:** Agents can see that a statement period exists even when the rendered PDF is unavailable, instead of being unable to distinguish a suppressed statement from a missing one. Multi-year loan histories are no longer truncated in the portal view.

**How to use it:** No action needed — applies automatically to the Agent Portal CRM documents dialog and the loan-level Statements view. If you encounter a "PDF missing" entry that you expect to be renderable, the underlying statement record exists but no PDF was generated; check the document descriptor via the API to confirm whether PDF generation succeeded for that period.

### Bug Fixes

#### Negative `promoRates` are rejected at loan and draw origination

**Type:** Bug Fix
**Impact:** Recommended Action
**Area:** Loan Configuration

Fixed an issue where loan and draw origination accepted negative values for `promoRates`, producing invalid promo-rate configurations downstream. Origination requests with negative `promoRates` now return `400 Bad Request` with a message identifying the rejected value, while negative non-promo spread rates remain accepted as before.

If your origination flow was passing negative `promoRates` — whether intentionally to model spread-style discounts or inadvertently as part of a default-zero-then-decrement path — update the request to use non-negative values before sending. Existing loans created with negative promo rates prior to this fix are not affected.

#### `GET /events` returns `400` (not `500`) for malformed external-ID filters

**Type:** Bug Fix
**Impact:** Informational
**Area:** Developer Tools

Fixed an issue where `GET /events` filter values like `personId=ext-` (an `ext-` prefix with no ID after it) returned `500 Internal Server Error` instead of a client-error response. These requests now return `400 Bad Request` with a parameter-specific message identifying the malformed value and which filter parameter contained it.

#### Inbound Twilio voice webhooks no longer 500 when caller ID is suppressed

**Type:** Bug Fix
**Impact:** Informational
**Area:** Communications

Fixed an issue where inbound Twilio voice webhooks returned `500 Internal Server Error` when the caller's `From` value was caller-ID-suppressed (e.g., `Anonymous`, `Restricted`) or otherwise non-numeric. These calls now return `200 OK` and create an `Interaction` record with no `contact_id` or `person_id` attached, and a warning is logged for the unparseable phone value.

## Week of April 13, 2026

This week brings configuration improvements for NSF fees, bug fixes for address persistence and autopay, and new error-codes documentation.

### New Features

#### New Error Codes & Error Handling reference page

**Type:** New Feature
**Impact:** Informational
**Area:** Developer Tools

A new reference page documents Peach's error response schema, eleven common error scenarios with troubleshooting steps, and best practices for error handling in client integrations. Previously, error responses were undocumented in the public API reference.

Use the page when implementing retry logic, surfacing errors to borrowers or agents, or triaging unexpected API responses. Applies automatically - no action needed.

See the [Error Codes & Error Handling reference](/developer-tools/error-codes) for current behavior.

### Improvements

#### NSF dynamic fees support `passedAtOrigination` amount logic

**Type:** Improvement
**Impact:** Informational
**Area:** Loan Configuration

NSF dynamic fee types now accept `amountLogic.type=passedAtOrigination`, alongside the existing `fixed` option. Previously, NSF fees only supported a fixed amount configured on the loan type.

This lets you set NSF fee amounts per loan at origination time - for example, when fee size depends on borrower-specific terms - instead of forcing a single fixed value across every loan of a given type.

Configure `amountLogic.type=passedAtOrigination` when adding an NSF entry under the loan type's dynamic fees, then pass the per-loan amount during loan creation.

#### LOC migration period start dates are validated against the loan's activation date

**Type:** Improvement
**Impact:** Informational
**Area:** Loan Configuration

LOC migration requests now reject configurations where the migration period's `startDate` is earlier than the LOC's `activated_at` date, returning a validation error instead of accepting the request. Previously, migrations could create period data that pre-dated the loan itself, producing inconsistent billing-cycle records.

If a migration that previously succeeded now returns a validation error, adjust the migration period `startDate` so it falls on or after the LOC `activated_at` timestamp.

#### Replica v3 migration guide documents Amazon S3 Push Delivery

**Type:** Improvement
**Impact:** Informational
**Area:** Data & Reporting

The Replica v3 Technical Migration Guide now includes the full Amazon S3 Push Delivery section, mirroring the equivalent section in the Data Delivery Setup Guide. If you are migrating to Data Replica v3 with S3 as your delivery target, the OIDC provider configuration, IAM role setup, and bucket prerequisites are now covered inline in the migration guide.

See the [Replica v3 Technical Migration Guide](/release-notes/change-notices/replica-v3-migration-guide).

### Bug Fixes

#### Inline `personAddress` payloads now persist `postalCode`, `countyOrRegion`, and `POBox`

**Type:** Bug Fix
**Impact:** Informational
**Area:** Borrowers

Fixed an issue where loans created with an inline `personAddress` payload — rather than a referenced `personAddressId` — silently dropped the `postalCode`, `countyOrRegion`, and `POBox` fields.

#### Legal representative address updates now persist every address field

**Type:** Bug Fix
**Impact:** Informational
**Area:** Borrowers

Fixed an issue where updating a legal representative through the create or update endpoints did not normalize the `address` payload through the same deserializer used at create time, causing fields such as `timezone` to be dropped on update.

#### Final autopay amount on installment loans is no longer inflated when payments are processed but not yet settled

**Type:** Bug Fix
**Impact:** Informational
**Area:** Payments

Fixed an issue where the autopay recalculation on installment loans could inflate the final autopay amount when an autopay payment had been processed but not yet settled, particularly when autopay dates did not align with expected payment due dates.

#### Bulk operation detail rows in the CRM show counts derived from operation totals

**Type:** Bug Fix
**Impact:** Informational
**Area:** Servicing & Operations

Fixed an issue in the CRM where the *Successful*, *Failed*, and *Ineligible* rows on a bulk operation's detail page displayed the number of currently loaded results instead of the operation's true totals.

## 2026 Q1 — Released 04.10.2026

### Loan Management & Borrower Portal

- **White labeled borrower experience based on loan type** [New Feature] — Allow lenders to support multiple white-labeled Borrower Portal experiences based on their loan type configurations. Lenders that operate with multiple brands can have customized branding, support information, and UI elements for their loan types. For reference, please see this [Docs Hub guide](/white-labeling).
- **Issue rewards service credit transaction** [Improvement] — Allow lenders to issue "rewards" service credits type and optionally provide a custom name for that service credit transaction. For reference, please see this [API endpoint](https://docs.peachfinance.com/api-docs/api-public/transactions).
  - *Related training: [Borrower Portal - Agent-only View and Tools > Loan Options - Agent-only - Issue credit (updated)](/servicing-operations/agent-only-tools/agent-tools-overview)*
- **Enhanced Metro2 credit reporting logic related to bankruptcy** [Improvement] — Several updates to Peach's credit bureau reporting specifically around "Actual Payment Amount", "Account Status", "Payment History Profile", "Consumer Information Indicator", "FCRA Compliance / Date of First Delinquency", and terminal reporting logic. These changes ensure more accurate Metro2 reporting during the loan's bankruptcy lifecycle. For reference, please see this [Docs Hub guide](/credit-reporting).
- **Enhanced line of credit `paidOff` transition logic** [Improvement] — Refined the loan status transition logic to prevent line of credit loans from transitioning to `paidOff` status if there is an active Bankruptcy case (`case.status` = `initiated`, `processing`, `reopened`). A line of credit will only transition to `paidOff` when the line is closed, the balance is $0, there are no pending purchases or transactions, and there are no active Bankruptcy cases associated with the line.
- **Refined charge-off management** [Improvement] — Allow lenders to update the `chargedOffReason` on charged off loans. The following charged off reasons are supported: `term`, `bankruptcy`, `fraudulent`, `legal`. For reference, please see this [API endpoint](https://docs.peachfinance.com/api-docs/api-public/loans).
- **Future dynamic fee suppression for loans with active SCRA plan** [Improvement] — Allows lenders to suppress future dynamic fees when `isCancelFees=true`. This means in addition to waiving all past fees within the SCRA period, Peach will proactively prevent any future dynamic fees from being charged for the remainder of the active plan duration. This eliminates the need for manual future fee waivers for both installment and line of credit loans. For reference, please see this [API endpoint](https://docs.peachfinance.com/api-docs/api-public/scra).
- **Set and manage SCRA plans for lines of credit** [New Feature] — Allow lenders to create and manage SCRA plans on line of credit loans using the "Set line of credit SCRA terms" and "Manage line of credit SCRA terms" tools in the Agent-view of the Borrower Portal. When creating an SCRA plan, agents can set a new annual interest rate, an effective date, an end date, optionally waive all fees, and send a notice to the borrower. When managing an existing SCRA plan, agents can update the start date and end date of the plan. For reference, please see this [API endpoint](https://docs.peachfinance.com/api-docs/api-public/scra).
  - *Related training: [Borrower Portal - Agent-only View and Tools > Loan Options - Agent-only - Set SCRA terms (new)](/servicing-operations/agent-only-tools/agent-tools-overview)*
  - *Related training: [Borrower Portal - Agent-only View and Tools > Loan Options - Agent-only - Manage SCRA terms (new)](/servicing-operations/agent-only-tools/agent-tools-overview)*
- **Fail multiple transactions in a single API call** [New Feature] — Allow lenders to fail multiple transactions on a loan in a single request, triggering exactly one replay instead of one per transaction. This is significantly faster for lenders who process bulk ACH returns or need to fail several transactions on the same loan at once. The endpoint accepts an array of transactions with failure reason, optional processor details, and optional ACH return code. Supports `sync=true` to wait for the replay to complete. For reference, please see this [API endpoint](https://docs.peachfinance.com/api-docs/api-public/transactions).
- **Enhanced line of credit Purchase status transitions** [Improvement] — Allow lenders to change line of credit Purchase status from declined→authorized and settled→canceled.


### Admin Portal

- **New Admin Portal layout** [New Feature] — A new sidebar-based layout is now the default for the Admin Portal. The new layout replaces the top navigation bar with a sidebar that organizes features into sections: Workforce (Teams, Employees, Roles), Customer Messaging (Templates, Repayment Engine), Loan Types, Dynamic Fee Types, API Keys, Loan Labels, and Company Config. To return to the legacy layout, click the option at the bottom left of the sidebar. Capital Markets (Investors, Settlement Instruments) will be added to the sidebar in a subsequent update.
  - *Related training: [Admin Portal - Overview (updated)](/servicing-operations/admin-portal/admin-portal-overview)*
- **Employee assignment to investors** [New Feature] — Allow lenders to assign employees to one or more investors. If an employee is assigned to one or more investors, they can access all loans linked to those investors; if they are not assigned to any investor, they can access all loans across the company. This controls employee access to loan portfolios across the Peach system.
  - *Related training: [Admin Portal - Employee Management (new)](/servicing-operations/admin-portal/admin-portal-overview)*
- **Templates redesign** [New Feature] — Allow lenders to create both custom and loan-type-specific templates. The redesigned interface introduces a searchable template list with column management, a side panel with Template, Code, and Details tabs, and a new editor that displays all supported template variables by subject so that lenders know which variables can be used when drafting the content of each template. Additional features include export all templates, PDF preview with sample data rendering, and search filtering.
  - *Related training: [Admin Portal - Customer Messaging - Templates (updated)](/servicing-operations/admin-portal/admin-portal-overview)*
- **Repayment engine redesign** [New Feature] — Allow lenders to create, update, and delete repayment engine configurations for the 16 subjects offered today. Additional features include an updated main view, side panel, column management, and search filtering.
  - *Related training: [Admin Portal - Customer Messaging - Repayment Engine (updated)](/servicing-operations/admin-portal/admin-portal-overview)*
- **Company config redesign** [New Feature] — Allow lenders to view and manage their company configuration. Lenders can view the full configuration in a structured field view or raw JSON code view, and edit a subset of fields including UI elements, task management, support details, communication settings, and links.
  - *Related training: [Admin Portal - Company Config (new)](/servicing-operations/admin-portal/admin-portal-overview)*
- **Investors and settlement instruments management** [New Feature] — Allow lenders to create and manage their own investors and settlement instruments directly from the Admin Portal. Lenders can create investors with legal name, external ID, contact information, mailing address, and a default investor designation. Each investor can have one or more settlement instruments, which represent bank accounts where Peach settles payment proceeds. Both views include search filtering and column management. For reference, please see the [Investors](https://docs.peachfinance.com/api-docs/api-public/investors) and [Settlement Instruments](https://docs.peachfinance.com/api-docs/api-public/settlement-instruments) API endpoints.
  - *Related training: [Admin Portal - Capital Markets - Investors (new)](/servicing-operations/admin-portal/admin-portal-overview)*
  - *Related training: [Admin Portal - Capital Markets - Settlement Instruments (new)](/servicing-operations/admin-portal/admin-portal-overview)*