Documents
A document is evidence, so its bytes never change, its content is unreachable until a scan clears it, and its checksum is re-verified on the way out.
A document is what somebody will want to look at when the file is being argued over—a pay statement, an identity card, a signed agreement. Everything about how the Lending Core stores one follows from that: bytes that cannot be swapped, content that is unreachable until a scan has cleared it, and a checksum re-checked on the way out. Evidence that can be quietly replaced is worse than no evidence, because it will be believed.
An unlinked document cannot exist
POST /v1/documents (CREATE_LMS_DOCUMENT) is a single multipart request with two parts: a JSON metadata part and the file part. At least one CUSTOMER or LOAN link is required at upload, so nothing lands in a general pile that no borrower or loan can be traced to. The server derives the checksum and content type from the bytes; caller-asserted values cannot disagree with the file, which is what stops an executable arriving labeled as a PDF and being served back to a reviewer as one.
curl -X POST "$BASE/v1/documents" \
-H "Authorization: Bearer $TOKEN" \
-H "LendEasy-Tenant: demo-lender" \
-H "Idempotency-Key: harbor-pay-statement-april" \
-F 'metadata={"title":"Pay statement — April","documentType":"INCOME_EVIDENCE","sensitive":false,"links":[{"targetType":"LOAN","targetRef":"7204"}]};type=application/json' \
-F 'file=@pay-statement-april.pdf;type=application/pdf'
{ "resourceId": 1204 }
documentType is an UPPER_SNAKE classification code. The optional sensitive flag restricts reads to callers holding the additional sensitive read permission—and setting it requires that same permission, so a caller cannot restrict content they could not themselves read. An optional supersedesDocumentId records that this upload replaces an earlier document, because content is immutable: a corrected file is always a new document. The superseded copy is the one the borrower actually received, and overwriting it in place would leave a dispute about what they were sent with nothing to consult.
Nothing is downloadable until it is clean
The checksum is re-verified on the way out
GET /v1/documents/{documentId} (READ_LMS_DOCUMENT) returns metadata, scan status, checksum, and active links; sensitive documents additionally require the sensitive read permission.
{
"documentId": 1204,
"documentRef": "doc_4f2a9c1b8d3e5a7c9b1d",
"documentType": "INCOME_EVIDENCE",
"title": "Pay statement — April",
"origin": "STAFF_UPLOAD",
"originalFileName": "pay-statement-april.pdf",
"contentType": "application/pdf",
"contentLength": 184220,
"checksumSha256": "2d8c1f0e5b7a4c6d8e0f2a4b6c8d0e1f3a5b7c9d1e3f5a7b9c1d3e5f7a9b188a",
"scanStatus": "CLEAN",
"status": "ACTIVE",
"sensitive": false,
"createdAt": "2027-05-01T10:40:00Z",
"links": [{ "linkId": 61, "targetType": "LOAN", "targetRef": "7204", "active": true }]
}
GET /v1/documents/{documentId}/content streams the original bytes as an attachment after re-verifying the stored checksum, so storage that has decayed under a document is caught at the moment somebody asks for it rather than handed over as though it were intact. The response is never cached.
GET /v1/customers/{customerId}/documents and GET /v1/loans/{loanId}/documents list documents actively linked to that target, newest first. The loan listing includes system-generated documents: every completed statement is also a loan-linked document, so the statement PDF a borrower received is discoverable alongside uploaded evidence. See Statements.
Metadata changes; content never does
PUT /v1/documents/{documentId}(UPDATE_LMS_DOCUMENT) updatestitle,description, or thesensitiveflag and returns the command response with the changed fields. Content, origin, and checksum are immutable.POST /v1/documents/{documentId}/linksadds a link to another customer or loan ({ "targetType": "CUSTOMER", "targetRef": "412" });POST /v1/documents/{documentId}/links/{linkId}/removedeactivates one link with an optionalreasonCode, retaining the link row and its removal.POST /v1/documents/{documentId}/archive(ARCHIVE_LMS_DOCUMENT) takes no body and is idempotent; content and history are retained. A document’sstatusisACTIVE,ARCHIVED, or—once retention policy disposes of it—DISPOSED.GET /v1/documents/{documentId}/historyreturns the document’s change events—creation, metadata updates, link changes, archive—newest first. Document activity also appears in the linked customer’s ledger under theDOCUMENTcategory; see Customer lifecycle.
Case evidence lives in the servicing workspace
These routes are the Lending Core’s document store, and they hold what belongs to a borrower and a loan. Servicing cases have their own evidence pipeline under /v1/servicing/...—registration, short-lived upload targets, structured review, and retention holds—documented in Documents & secure upload. Keeping the two apart is what lets a case’s retention hold expire without touching the signed agreement the loan still needs.
sensitive flag for restricted evidence and keep credentials and card data out of documents entirely.