A loan tape is a scheduled CSV export that delivers loan or payment data to an investor over SFTP. When an investor owns only part of your portfolio, you scope a tape to that investor so its file contains only the loans associated with them. This runbook walks an integration engineer through configuring an investor-filtered tape, generating it, and confirming that the file was produced and delivered.
The filter is set once, when you create the tape: a tape carries a single investorId, and every time that tape generates, Peach includes only the loans currently associated with that investor.
This page covers the procedure end to end — from the prerequisite loan-investor association through confirming a delivered file. It does not cover:
- The full SFTP, encryption, and file-naming configuration — see Set up data delivery.
- The Admin Portal equivalent of this API flow — see Manage loan tapes and loan tape types.
- The field-level contract for every tape column — see Loan tape field reference and the Loan Tapes API.
An investor record exists for your company, and you have its
investorId(formatIR-XXXX-XXXX). Retrieve it from the Admin Portal; the programmatic investor-list endpoint is Peach-internal, not part of the public API.Each loan that should appear on the tape is associated with that investor. A loan joins the tape only through an active
LoanInvestorAssociation; loans with no association to the tape's investor are excluded. Set the association withPUT /api/people/{personId}/loans/{loanId}/investors:PUT /api/people/{personId}/loans/{loanId}/investors Content-Type: application/json { "effectiveDate": "2026-05-01", "investors": [ { "investorId": "IR-7F3A-9K2D", "share": 1.0 } ] }investorIdandshareare required per investor;shareis the ownership fraction in decimal form, where1.0is 100% and0.15is 15%. Thisshareis what the tape reports asinvestorOwnershipPercentage.A loan tape type is configured, or you create one in Step 1. The tape type defines which fields the CSV carries, its money unit, and its reporting rules.
An SFTP destination is reachable, and a GPG public key is available if you encrypt the file. These credentials are supplied by the receiving investor or your own delivery team — Peach does not generate them.
Permissions on your API credential:
loan.investors:update,loan.tape.type:create,loan.tape:create,loan.tape:generate, andloan.tape.record:list.
- Loan tape field reference — the loan and payment columns a tape can carry, and where each comes from.
- Snapshots and the replica database — tapes read each loan's daily snapshot, so a loan with no snapshot for the report date cannot be reported.
- Manage loan tapes and loan tape types — the Admin Portal view of the same objects.
- Loan tape type — the reusable template that defines a tape's fields, money unit, and reporting rules. Many tapes can share one type.
- Loan tape — a single delivery configuration: one tape type plus one SFTP destination and, optionally, one
investorIdfilter. - Investor association — the
LoanInvestorAssociationlinking a loan to an investor with an ownershipshare; it is what the investor filter joins against. - Snapshot dependency — each tape row is built from the loan's snapshot for the report date; missing snapshots are handled by the tape type's
actionOnMissingDatasetting. - Asynchronous generation — generating a tape fires a background job and returns immediately with an
operationId; the file is produced and uploaded by a worker, and you observe completion through the tape's records.
The diagram below shows the full sequence: the prerequisite association, configuring the tape type and the investor-filtered tape, generating asynchronously, and monitoring the records.
A tape type fixes the column set and reporting rules. Create one if you don't already have a suitable type, or list existing types with GET /api/companies/{companyId}/loan-tape-types and reuse one.
POST /api/companies/{companyId}/loan-tape-types
Content-Type: application/json
{
"tapeName": "Investor portfolio — loans",
"tapeType": "loans",
"fields": [
"reportDate",
"loanId",
"investorId",
"investorOwnershipPercentage",
"loanStatus",
"outstandingBalanceTotalAmount"
],
"moneyUnit": "subUnit100",
"reportDaysWhenPaidOff": 10
}tapeType, tapeName, and fields are required. Amounts default to subUnit100 (integer pennies); set moneyUnit to fullUnit for decimal dollars. reportDaysWhenPaidOff has a minimum of 10, and any lower value is raised to 10.
For the complete field catalog and per-field meanings, see the Loan tape field reference — do not enumerate all fields here.
Expected result: The response returns 201 with the new loanTapeTypeId (format TY-XXXX-XXXX). Save it for Step 2.
Create the tape that ties the tape type to a delivery destination, and set investorId to scope it to one investor. This is the switch that turns a portfolio-wide tape into an investor-filtered one.
POST /api/companies/{companyId}/loan-tapes
Content-Type: application/json
{
"loanTapeTypeId": "TY-4M8X-2P9Q",
"investorId": "IR-7F3A-9K2D",
"sftpHostname": "sftp.example-investor.com",
"sftpAccessMethod": "usernamePassword",
"sftpUsername": "<your-sftp-username>",
"sftpPassword": "<your-sftp-password>"
}loanTapeTypeId and sftpHostname are required. When sftpAccessMethod is usernamePassword (the default), supply sftpUsername and sftpPassword. To encrypt the file, set encryptionMethod to GPG and provide the investor's publicKey.
sftpPassword, sftpUsername, and publicKey are real request fields that carry secrets supplied by the receiving investor. Use a secret manager and clearly-marked placeholders in any shared example. Never commit a literal password or key to a repository, ticket, or document.
Leaving investorId unset produces a tape covering every loan for the company; setting it restricts the tape to that one investor for the life of the tape.
Expected result: The response returns 201 with the new loanTapeId (format TA-XXXX-XXXX) and echoes the investorId you set. Save the loanTapeId for Step 3 and for monitoring.
Generate the tape for one or more report dates. Generation is asynchronous: the call validates the request, fires one background job per tape and report date, and returns immediately. It does not return the file.
POST /api/companies/{companyId}/loan-tapes/generate
Content-Type: application/json
{
"loanTapeIds": ["TA-DJEK-CVDE"],
"reportDates": ["2026-05-31"],
"forceReplace": false,
"uploadFile": true
}reportDates is required (ISO YYYY-MM-DD). If you omit loanTapeIds, Peach generates every tape configured for the company. forceReplace (default false) creates a new record even when one already exists for that tape and date; uploadFile (default true) controls whether the worker uploads the file to SFTP after building it.
Expected result: The response returns 201 with one entry per tape and date, each carrying a loanTapeId, the reportDate, and an operationId you can use to trace the background job.
{
"data": [
{
"loanTapeId": "TA-DJEK-CVDE",
"reportDate": "2026-05-31",
"operationId": "8c1f0e6a-2d4b-4f9a-9b27-1a2b3c4d5e6f"
}
]
}Poll the tape's records to track the job and confirm delivery. Records are returned sorted by createdAt descending, so the most recent generation is first.
GET /api/companies/{companyId}/loan-tapes/{loanTapeId}/recordsRead the record's status to determine where the job is:
status | Meaning |
|---|---|
null | Generation is in progress — the record was created when the job triggered and has not reached a terminal state. |
created | The file was built but has not yet been uploaded to SFTP. |
succeeded | The tape was generated and, if configured, uploaded. |
failed | Generation failed; read failureDescription for the reason. |
The status moves through these states as the worker runs:
When status is succeeded, confirm:
tapeFileNameis populated and matches the file delivered to the SFTP destination.- The record's
investorIdmatches the investor you scoped the tape to. - The file contains only loans associated with that investor. A loan that should appear but is missing almost always lacks an active
LoanInvestorAssociationto this investor.
- Generation runs in a worker. The API call only triggers the job. The worker resolves the loan set, builds the CSV, and uploads it; you observe progress through the records, not the generate response.
- Loan inclusion is status-scoped. A loan is included when its status is
Active,Accelerated,Frozen, orChargedOff, or when it isPaidOffand was paid off within the tape type'sreportDaysWhenPaidOffwindow. - Missing snapshot data is governed by
actionOnMissingData. The effective default isincludeIDsAndEmptyData: a loan with no snapshot for the report date is still reported, with only its identifier columns populated anddataStatusset tomissingData. WithexcludeFromTape, such loans are dropped from the file entirely. - Line-of-credit draws can be reported as separate rows when the tape type sets
shouldReportDrawsSeparately; otherwise an LOC is reported on an aggregated level.
- The investor filter depends on the association being present and not soft-deleted. Known limitation. The worker joins loans to the investor through
LoanInvestorAssociationand excludes associations with adeleted_atvalue. A loan removed from the investor, or never associated, silently does not appear — the tape does not warn you. - Generation is asynchronous. By design. The generate call returns
operationIds, not a file. Treat the recordsstatusas the source of truth for completion. - One tape filters to one investor. By design.
investorIdis a single, nullable value on the tape. To deliver to several investors, create one tape per investor; leaveinvestorIdblank only when you want every company loan on one file. excludeFromTapeshifts validation onto you. By design. When a tape type usesexcludeFromTape, loans with missing data are dropped without notice, so you are responsible for reconciling the loan count against your own records.- Membership-fee columns are gated. On the roadmap. The
*MembershipFeesAmounttape fields populate only when the membership-fee feature is enabled.
statusisfailed. ReadfailureDescriptionon the record. A common cause is an SFTP problem — an unreachable host, a rejected credential, or a missing target directory. Correct the tape's SFTP settings (or the destination), then regenerate.- The tape is empty or missing expected loans. The loans most likely have no active
LoanInvestorAssociationto this investor, or they fall outside the included statuses, or they have no snapshot for the report date and the tape type usesexcludeFromTape. Verify the associations first, then the loan statuses, then the snapshots. - A record already exists for that tape and date. Re-running generate without
forceReplacewill not overwrite an existing record. SetforceReplace: trueto create a fresh record for the same date. - Generation never reaches a terminal state. A
statusthat staysnullfor an extended period points to a stalled worker job; use theoperationIdfrom the generate response when contacting Peach support.
Why does the tape include fewer loans than I expect? A loan appears only if it has an active association to the tape's investor and falls within the included statuses and (under excludeFromTape) has a snapshot for the report date. Check those three conditions in that order.
I generated the tape but no file arrived at the SFTP destination. Check the record status. If it is created, the file was built but the upload has not completed or failed; if it is failed, read failureDescription. Confirm you generated with uploadFile: true — false builds the record without delivering a file.
Can I change which investor a tape reports? The investorId is part of the tape's configuration. Update the tape, or create a new tape for the other investor. The association data itself is changed through PUT /api/people/{personId}/loans/{loanId}/investors.
The amounts look off by a factor of 100. Amounts default to subUnit100 (pennies). Set the tape type's moneyUnit to fullUnit for decimal dollars.
| Action | Method | Endpoint |
|---|---|---|
| Associate loan with investor | PUT | /api/people/{personId}/loans/{loanId}/investors |
| Create loan tape type | POST | /api/companies/{companyId}/loan-tape-types |
| List loan tape types | GET | /api/companies/{companyId}/loan-tape-types |
Create loan tape (set investorId) | POST | /api/companies/{companyId}/loan-tapes |
| Generate loan tapes | POST | /api/companies/{companyId}/loan-tapes/generate |
| List loan tape records | GET | /api/companies/{companyId}/loan-tapes/{loanTapeId}/records |
For full field contracts, see the Loan Tapes API, Loan Tape Types API, and Loan investors API.
- Loan tape field reference — every loan and payment column a tape can carry.
- Snapshot field reference — the daily loan state that tape rows are built from.
- Set up data delivery — SFTP, encryption, and file-naming configuration.
- Manage loan tapes and loan tape types — the Admin Portal equivalent of this flow.
- Loan Tapes API — the canonical endpoint reference.