A lending API for the
whole loan lifecycle
Versioned REST under /v1, idempotency keys on every mutation, HMAC-signed webhooks, sandbox tenants, and generated docs. Our own operator console is a client of this API. If the UI can do it, your code can do it.
# Create a loan from an approved application POST /v1/applications/app_7c21/loans Idempotency-Key: 41d8-a2f0-77b3 { "product_id": "prod_payday_ca", "amount_cents": 40000, "due_anchor": "payday" } 201 Created { "id": "ln_8kq3", "status": "originated", "due_date": "2026-08-29", // tenant TZ "balance_cents": 45800 // derived }
✓ loan.originated ln_8kq3 · 09:14:02 ✓ document.signed doc_88f2 · 09:14:31 ✓ disbursement.settled le_0161 · 09:15:04 ✓ payment.scheduled pay_02Yw · 09:15:05 ✓ payment.executed pay_02Yw · Aug 29 ✓ loan.paid ln_8kq3 · Aug 29 # Signed, retried with backoff, replayable # from the dashboard. Polling is optional, # forever.
Boring by principle
We integrated a lending API that had none of this, and paid for it in production incidents. Lendfy's API is the one we needed on the other side.
Honest writes
A 2xx means the thing happened, verified. Failures are failures with structured bodies. No success theater, ever.
Idempotency everywhere
Every mutation accepts an idempotency key. Retry storms, double-clicks, and replayed jobs can't double-charge or double-fund.
One convention
One naming scheme, one date format (with explicit offsets), integer cents, cursor pagination, versioned under /v1. Learn it once.
Events, not polling
HMAC-signed webhooks with retries and replay, plus a per-entity activity stream. Build reactive integrations, not cron jobs.
Sandbox & dry runs
Full-parity sandbox tenants and preview modes on dangerous operations. Originate a fake loan end to end before you touch production.
Docs generated from truth
OpenAPI reference generated from the running code. The docs can't drift from the API because they are the API.
The console is a client of this API
The Lendfy operator console is built entirely on the endpoints documented here. There is no privileged internal interface, which is why an integration can do everything a user can do.
If a screen can do it, an endpoint does it. There is no hidden admin path your integration can't take.
/v1 stays /v1. A breaking change ships as a new version with a changelog and a migration window, never as a surprise.
API keys and OAuth client-credentials for services, scoped per tenant, with rotation that doesn't require a support ticket.
POST /v1/loans/ln_8kq3/payments 422 Unprocessable { "error": { "type": "schedule_conflict", "message": "A pending collection already exists for 2026-08-21.", "existing": "pay_02Yw", "retryable": false } } # Typed errors. A conflict is not a 500. # An empty list is not an auth failure.
Build on rails that hold
Get sandbox access, originate a fake loan end to end, and watch the webhooks arrive. Then decide.