> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Quick start

> Six calls from an empty database to a disbursed loan — a product, a version, an accounting profile, an application, an approval, and a disbursement.

This page takes you from an empty database to one disbursed loan in six calls. Every call sends `application/json`. **Send every money and rate field as a decimal string**, never as a JSON number.

Complete the [Prerequisites](/en/lender/lender-prerequisites) first. Send the same bearer token on all six calls. Lender takes the officer from the token subject. Under the generic profile, only that officer can approve and disburse the application.

<Info>
  Use jurisdiction `XX` for this walkthrough. `XX` is Lender's generic profile, and it computes no withholdings — which keeps the amounts in step 6 simple. A Brazilian regulated loan carries CET disclosure and capitalization consent. Read the [Brazil regulatory pack](/en/lender/brazil-regulatory-pack) for that path. A payroll-deducted contract belongs to the consignado privado bounded context. Read [Consignado privado](/en/lender/consignado-privado) for its vocabulary and its lifecycle topics.
</Info>

## The six calls

***

```
1. POST /api/v1/loan-products                          → productId
2. POST /api/v1/loan-products/{productId}/versions     → versionId
3. POST /api/v1/loan-products/{productId}/accounting-profiles
4. POST /api/v1/loan-applications                      → pending_approval
5. POST /api/v1/loan-applications/{id}/approve         → approved
6. POST /api/v1/loan-applications/{id}/disburse        → disbursed
```

## 1. Create the product

***

`POST /api/v1/loan-products`

```json theme={null}
{
  "name": "Personal loan — generic",
  "loanType": "personal",
  "jurisdictionCode": "XX"
}
```

`loanType` takes `personal`, `commercial`, or `card`. `jurisdictionCode` takes a code the registry carries: `XX` or `BR`. Any other code returns 422.

The response carries the new `id`. The product is in `draft`, which is all this walkthrough needs: an application binds to a *version*, so you do not activate the product to originate.

## 2. Create a version

***

`POST /api/v1/loan-products/{productId}/versions`

```json theme={null}
{
  "jurisdictionCode": "XX",
  "changeReason": "initial version",
  "currency": "USD",
  "rateMode": "fixed",
  "fixedAnnualRateBps": 1800
}
```

The version is the immutable snapshot of terms an application binds to.

* **`currency` has no default.** Supply a valid ISO-4217 code in uppercase. The version is the currency source of truth from here to the ledger.
* **A fixed version carries no floating linkage.** With `rateMode: fixed`, omit `floatingRateTableId`, `floatingSpreadBps`, and `requiresFloatingRate`. Lender rejects a fixed version that carries any of them.
* **`jurisdictionCode` is required**, and it must match the product's. A version cannot move a product to another jurisdiction.

The response carries the `versionId`.

## 3. Bind an accounting profile

***

`POST /api/v1/loan-products/{productId}/accounting-profiles`

The profile maps each accounting event onto general-ledger accounts. Lender needs it at disbursement, so bind it now.

```json theme={null}
{
  "loanProductVersionId": "<versionId>",
  "accountingMode": "accrual",
  "postingRules": [
    {
      "eventType": "disbursement",
      "legs": [
        { "account": "1100.10.001", "role": "principal", "side": "debit" },
        { "account": "1000.10.001", "role": "cash", "side": "credit" }
      ]
    },
    {
      "eventType": "repayment",
      "legs": [
        { "account": "1000.10.001", "role": "cash", "side": "debit" },
        { "account": "1100.10.001", "role": "principal", "side": "credit" }
      ]
    },
    {
      "eventType": "prepayment",
      "legs": [
        { "account": "1000.10.001", "role": "cash", "side": "debit" },
        { "account": "4200.10.001", "component": "charge_rebate", "side": "debit", "optional": true },
        { "account": "4300.10.001", "component": "iof_refund", "side": "debit", "optional": true },
        { "account": "1100.10.001", "role": "principal", "side": "credit" },
        { "account": "2400.10.001", "component": "iof_due", "side": "credit", "optional": true }
      ]
    },
    {
      "eventType": "accrual",
      "legs": [
        { "account": "1200.10.001", "role": "interest", "side": "debit" },
        { "account": "4100.10.001", "role": "interest", "side": "credit" }
      ]
    },
    {
      "eventType": "collection_unapplied",
      "legs": [
        { "account": "1000.10.001", "role": "cash", "side": "debit" },
        { "account": "2100.10.001", "role": "unapplied_cash", "side": "credit" }
      ]
    },
    {
      "eventType": "collection_reapply",
      "legs": [
        { "account": "2100.10.001", "role": "unapplied_cash", "side": "debit" },
        { "account": "1100.10.001", "role": "principal", "side": "credit" }
      ]
    },
    {
      "eventType": "collection_refund",
      "legs": [
        { "account": "2100.10.001", "role": "unapplied_cash", "side": "debit" },
        { "account": "1000.10.001", "role": "cash", "side": "credit" }
      ]
    }
  ]
}
```

Send **seven or eight rules**, one per accounting event. Seven events need a rule: `disbursement`, `repayment`, `prepayment`, `accrual`, `collection_unapplied`, `collection_reapply`, and `collection_refund`. `accrual_tax` is the optional eighth. Fewer than seven rules is rejected before the handler runs.

Each leg declares exactly one of `role` or `component`, and no account appears on both sides of the same rule. Four events also carry a fixed leg shape:

| Event                  | Legs                                                                                                                                                   |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `accrual`              | Exactly one debit and one credit.                                                                                                                      |
| `prepayment`           | Exactly five, in this order: `cash` debit, optional `charge_rebate` debit, optional `iof_refund` debit, `principal` credit, optional `iof_due` credit. |
| `collection_unapplied` | Exactly two: `cash` debit, `unapplied_cash` credit.                                                                                                    |
| `collection_refund`    | Exactly two: `unapplied_cash` debit, `cash` credit.                                                                                                    |

`collection_reapply` takes `unapplied_cash` as its only debit. Each credit it carries must also appear as a credit on `repayment` or `prepayment`, with the same account and the same role. `disbursement` and `repayment` each need at least one debit and one credit.

Keep the `disbursement` rule at two legs, as above. Lender fills the `cash` leg with the net amount and every other structural leg with the gross amount. A `component` leg takes a computed withholding, and the generic profile computes none, so the two-leg rule is the one that balances under `XX`.

In multi-tenant mode the profile also needs `midazOrganizationId` and `midazLedgerId`. Read [Define a loan product](/en/lender/define-a-loan-product) for the wider product surface.

## 4. Create the application

***

`POST /api/v1/loan-applications`

```json theme={null}
{
  "loanProductVersionId": "<versionId>",
  "borrowerId": "borrower-0001",
  "requestedPrincipalAmount": "50000.00",
  "requestedInterestRate": "0.01500000",
  "requestedInstallments": 24,
  "expectedDisbursementDate": "2026-08-01T12:00:00Z",
  "previewScheduleSnapshotId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
}
```

* `requestedInterestRate` is the **monthly** rate as a decimal string at scale 8. It must be greater than `0` and no greater than `1`. Lender builds the schedule from this rate: `"0.01500000"` a month is `1800` bps a year.
* `requestedInstallments` is between 1 and 600.
* `previewScheduleSnapshotId` is a UUID **you** generate to identify the quote you showed the borrower. Lender records it on the application. Compute the schedule you show with `POST /api/v1/loan-applications/preview-schedule`. That call persists nothing and returns no identifier. Mint the identifier on your side and keep it with your own quote record.
* There is no `assignedOfficerId` field. Lender sets it from the token subject.

The application comes back in `pending_approval`, carrying the jurisdiction code and profile version Lender resolved from the product version.

## 5. Approve

***

`POST /api/v1/loan-applications/{id}/approve`

```json theme={null}
{
  "approvedAmount": "48000.00",
  "decisionAt": "2026-08-01T12:00:00Z",
  "note": "within policy"
}
```

`approvedAmount` becomes the ceiling on everything you disburse. Under `XX`, only the officer who created the application may approve it. The application moves to `approved` and carries the decision record.

## 6. Disburse

***

`POST /api/v1/loan-applications/{id}/disburse`

Send the header **`X-Idempotency`**. Lender requires it. A retry with the same value replays the first response, and records no second disbursement.

```json theme={null}
{
  "loanAccountId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "grossRequestedAmount": "48000.00",
  "netDeliveredAmount": "48000.00",
  "disbursedAt": "2026-08-01T13:00:00Z"
}
```

<Warning>
  **Under `XX`, `netDeliveredAmount` must equal `grossRequestedAmount`.** The disbursement posting balances as net equals gross minus withholdings, and the generic profile computes no withholdings. Any lower net leaves the posting unbalanced and Lender rejects the disbursement.
</Warning>

`loanAccountId` is a UUID **you** supply — Lender does not mint it. It identifies the loan account this contract services under, and it is immutable across later tranches of the same application.

Lender also checks that:

* `netDeliveredAmount` does not exceed `grossRequestedAmount`.
* `grossRequestedAmount`, and the running total across tranches, does not exceed `approvedAmount`.
* `disbursedAt` is not before the approval decision.
* The jurisdiction and profile version still match the pair Lender resolved at step 4.

`originationFeeAmount` is optional. It carries cost metadata that accrual reads. Lender does not treat it as a withholding, so it does not change the net-to-gross relationship.

## Confirm the loan exists

***

The disburse response carries the application in `disbursed` with its disbursement event. Then read the loan account:

| Call                                                 | What it returns                                                                          |
| ---------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `GET /api/v1/loan-accounts/{loanAccountId}`          | The active loan account: status, principal balance, open balance, and disbursement date. |
| `GET /api/v1/loan-accounts/{loanAccountId}/schedule` | The origination schedule — one entry per installment, all unpaid.                        |

The response also carries `profileVersion` — the jurisdiction profile version, not a product version. It carries no currency. The loan uses the `currency` you set on the loan product version in step 1. Keep that value with your own product record.

If you configured Midaz, the disbursement posting reaches the ledger once the outbox dispatcher relays the intent. That happens shortly after the call, not inside it.

## Next steps

***

<Card title="Service a loan" icon="wrench" href="/en/lender/service-a-loan" horizontal>
  Record repayments, prepay, reschedule, and correct a live loan account.
</Card>

<Card title="How accrual works" icon="chart-line" href="/en/lender/how-accrual-works" horizontal>
  Interest recognition runs on its own schedule. Start an accrual run, or enable the heartbeat.
</Card>
