Lendfy API
A REST API for the whole loan book — the same API the Lendfy app runs on. Use it to bring customers in from another system, keep an external product in sync, or build your own tooling on top of your data.
Overview
The base URL for all requests:
https://api.lendfy.io/v1
Every request and response body is JSON (Content-Type:
application/json). All traffic requires TLS. Responses include an
X-Request-Id header — quote it when reporting an issue and we
can find the exact request in our logs.
Authentication
Authenticate with your organization's API key as a Bearer token. Keys
start with lfy_ and are scoped to your organization — you can
only ever see your own data.
curl https://api.lendfy.io/v1/customers \
-H "Authorization: Bearer lfy_your_api_key"
Errors
Errors use one envelope everywhere — a machine-readable
code, a human-readable message, and the request
id for correlation. Some errors attach extra fields (for example
existing_id on a duplicate conflict).
{
"error": {
"code": "duplicate_suspected",
"message": "This looks like Maria Lopez, who is already on file…",
"request_id": "req_8f31c2",
"existing_id": "7b0c…"
}
}
| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing or invalid API key. |
| 404 | not_found | No such resource in your organization. |
| 409 | duplicate_suspected | Customer create matched someone already on file. |
| 422 | customer_incomplete | Customer create is missing required fields (name, SSN, DOB, email, phone, address, employer, pay amount, pay frequency, bank routing + account). The missing list names each one. PATCH stays field-by-field. |
| 409 | idempotency_conflict | Idempotency-Key reused with a different body. |
| 422 | validation_error | A field failed validation — the message names it. |
Idempotency
Every write (POST, PATCH) requires an
Idempotency-Key header — any unique string, a UUID is
perfect. If a request is retried with the same key and body, the stored
response is replayed instead of the work running twice (the replay carries
X-Idempotent-Replay: true). The same key with a
different body returns 409 idempotency_conflict.
# a network timeout is safe to retry with the same key —
# the customer can never be created twice
curl -X POST https://api.lendfy.io/v1/customers \
-H "Authorization: Bearer lfy_…" \
-H "Idempotency-Key: 6f9c1a2e-4b7d-4c11-9a3f-2f8e5d6c7b01" \
-H "Content-Type: application/json" \
-d '{"first_name":"Maria","last_name":"Lopez"}'
Pagination
List endpoints are cursor-paginated. Pass limit (default
25, max 100); the response carries next_cursor — pass it back
as cursor for the next page. A null cursor means
you've reached the end.
{ "data": [ … ], "next_cursor": "MTcyNT…" }
Money, dates & PII
- Money is integer cents.
25500means $255.00 — no floats, ever. - Dates are
YYYY-MM-DD; timestamps are ISO-8601 UTC. Day-boundary math (due dates, days past due) runs in your organization's timezone. - Sensitive numbers go in but never come out. You may send a full SSN and full bank account number; Lendfy stores and returns only the last four of each (SSNs additionally keep a salted one-way hash used for duplicate matching). Phone numbers normalize to digits.
List & search customers
Newest first. query matches name and email
(case-insensitive contains), a 7+ digit string matches phone, and a
4-digit string matches SSN last-four.
| Parameter | Description |
|---|---|
| query | Search text — name, email, phone digits, or SSN last-4. |
| limit | Page size, 1–100. Default 25. |
| cursor | Cursor from the previous page. |
Retrieve a customer
{
"id": "7b0c1d2e-…",
"first_name": "Maria",
"last_name": "Lopez",
"email": "maria@example.com",
"phone": "8185550142",
"dob": "1990-04-12",
"ssn_last4": "6789",
"payroll_cadence": "biweekly",
"bank_routing": "021000021",
"bank_account_last4": "6789",
"store_id": "1111…",
"status": "active",
"external_source": null,
"external_ref": null,
"created_at": "2026-08-10T20:14:03Z",
"…": "address, employment, ID and consent fields omitted for brevity"
}
Create a customer
Only first_name and last_name are required;
everything else is optional and validated when present. Key fields:
| Field | Description |
|---|---|
| first_name required | 1–120 chars. |
| last_name required | 1–120 chars. |
| middle_name / suffix | Optional identity extras. |
| dob | YYYY-MM-DD. Borrowers must be 18+. |
| ssn | 9 digits (full) or 4 (last-four). Stored as last-4 + salted hash only. |
| email / phone | Light format check; phone normalizes to digits. |
| addr1, addr2, city, state, zip | Mailing address; 2-letter state. |
| payroll_cadence | weekly · biweekly · semimonthly · monthly |
| employer_name, employer_address, employment_start/end, pay_amount_cents | Employment & income — pay amount is PER PAYCHECK, in cents. Benefits customers put the program name in employer_name. Employer is required to originate. |
| payroll_cadence, last_pay_date | The payday anchor. Responses include a derived read-only next_pay_date. |
| status | good (default) · prospect · on_hold · payment_plan · active_military · bankrupt · legal · deceased · done_borrowing · inactive. Only good/prospect can originate loans. |
| bank_routing | 9 digits — the ABA check digit is verified. |
| bank_account | 4–17 digits. Only the last four are stored. |
| bank_account_type / bank_name / direct_deposit | Instrument details. |
| id_type, id_number, id_state, id_expires | Government ID. |
| sms_opt_in / email_opt_in / do_not_call | Contact consent flags. |
| store_id | Location the customer belongs to (must be yours). |
| external_source / external_ref | Your system's identifier — see importing. |
| allow_duplicate | Set true to override a duplicate_suspected 409. |
Duplicate protection
If the SSN — or name plus date of birth — matches a customer already on
file, the create returns 409 duplicate_suspected with
existing_id and existing_name so you can link to
the existing record instead. If it really is a different person, re-send
with "allow_duplicate": true.
Update a customer
Partial update: only the fields present in the body change. An explicit
null clears an optional field; names cannot be emptied.
Sending a new ssn or bank_account replaces the
stored last-four (the full numbers are still never stored). Every update
is audited with before-and-after values.
Importing a book from another system
Bringing customers over from a previous LMS? Stamp each record with your identifiers so the two systems stay linkable:
external_source— a short name for the system of origin (e.g."legacy_lms").external_ref— that system's customer id. The pair is unique per organization, so re-imports can't double-create.
Combine with Idempotency-Key per record and the duplicate
protection above, and an import script that crashes halfway is safe to
simply run again.
List loans
| Parameter | Description |
|---|---|
| customer_id | Only this customer's loans. |
| status | active · paid · pending · charged_off · void · canceled |
| bucket | Servicing bucket: past_due · due_today · due_week · current · payment_plan · returned |
| limit / cursor | Pagination as above. |
Loan rows carry principal, due date, the balance and days-past-due as
most recently reported, and any scheduled collection
(collection_scheduled_date / _method).
Retrieve a loan
Loan ledger
The loan's money history — disbursements, payments, adjustments — newest first, with the running balance. Amounts follow the cents convention: payments are negative, charges positive.
Other endpoints
The surface keeps growing with the product. Also available today:
GET /v1/servicing/summary— the whole book's KPIs in one call.GET /v1/collections/queue— past-due accounts bucketed by days late, with promise and schedule state.GET /v1/customers/{id}/timeline— notes, promises, and observed payments merged into one feed.POST /v1/customers/{id}/notesandPOST /v1/promises— collections activity.POST /v1/loans·/fund·/payments·/cancel·/void— origination and payments for Lendfy-native loans.GET/POST /v1/customers/{id}/cards— debit cards on file (brand + last-4 + expiry only; the PAN is never stored).GET/POST /v1/loans/{id}/documents(+/{doc_id}/download) — the loan file: signed agreements, receipts, disclosures. Upload is base64 JSON (kind,filename,content_type,data_b64, 10 MB cap); removal is soft, so a loan file never silently loses a contract.GET/POST /v1/loans/{id}/notes— the loan's own notes thread (also visible on the customer's timeline).GET/POST /v1/customers/{id}/references— personal references (relative/friend/coworker contact records;DELETEto remove).GET/POST /v1/customers/{id}/calls— the call log. Append-only: direction, outcome, phone, note.GET/POST /v1/customers/{id}/communications— sent emails/texts/letters, one history per customer. Append-only; log sends from external tools here.GET/POST /v1/customers/{id}/documents(+/{doc_id}/download) — customer-level uploads (IDs, statements); the listing also includes every loan-file document, labeled by loan.GET/POST /v1/customers/{id}/bank-accounts·/employers·/phones— multiple on file, exactly one primary each (POST …/{item_id}/primaryto switch; the primary mirrors onto the customer's flat fields). Account numbers: last-4 only.POST /v1/loans/{id}/schedule-payment(+ list/cancel) — ACH from the primary account or a card on file; stamps the loan's scheduled-collection fields.GET/POST /v1/loans/{id}/payment-plan(+/cancel) — the agreed installment split; must sum to the balance, one active plan per loan.GET /v1/integrations·PUT/DELETE /v1/integrations/dropbox-sign(+/test) — bring your own vendor keys. Keys are write-only: reads return a masked form.POST /v1/loans/{id}/esign/send·GET /v1/loans/{id}/esign(+/refresh,/cancel) — send a pending loan's agreement for e-signature through Dropbox Sign; the signed PDF files into the loan documents asesign_receipt. Webhook:POST /v1/webhooks/dropbox-sign.GET/PUT /v1/settings— tenant configuration.pay_frequenciescontrols the label and availability of each payroll cadence everywhere (keys are fixed; payday math depends on them).GET/POST /v1/api-keys(+DELETE /{id}) — API key management. The secret is returned once at creation; revocation is immediate, and the key authenticating the request cannot revoke itself.POST /v1/stores/{id}/zone— assign a store to a zone (region grouping); zones ride the store objects.GET /v1/exports/customers.csv·loans.csv·collections.csv— full-book CSV exports.