Skip to main content
This page takes you from an empty database to one disbursed loan in seven calls. Every call sends application/json. Send every money and rate field as a decimal string, never as a JSON number. Complete the Prerequisites first. Send the same bearer token on all seven calls. Lender takes the officer from the token subject, and only that officer can approve and disburse the application.
This walkthrough books a Brazilian loan with tax regime none, which computes no IOF and keeps the amounts in step 7 simple. Read the Brazil regulatory pack for a product that withholds IOF. A payroll-deducted contract belongs to the consignado privado bounded context. Read Consignado privado for its vocabulary and its lifecycle topics.

The seven calls


1. Create the product


POST /api/v1/loan-products
loanType takes personal, commercial, or card. jurisdictionCode is BR. Any other code returns 422. The response carries the new id. The product is in draft.

2. Create a version


POST /api/v1/loan-products/{productId}/versions
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.
  • rateBand is required. It is the price band the version authorizes, in annual basis points. An application whose rate falls outside it is refused.
  • admittedBorrowerKinds names who the version lends to. An empty or omitted list admits no borrower.
  • normPortfolioClass is the carteira. A Brazilian version without one cannot be activated in step 4.
  • 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. Activation refuses a product without one, so bind it now.
Send one rule per accounting event. Seven events need a rule: disbursement, repayment, prepayment, accrual, collection_unapplied, collection_reapply, and collection_refund. The other event types are optional. Fewer than seven rules is rejected before the handler runs. A product under Brazilian credit-loss rules also needs provision_constitution and provision_reversal, or its measured provision does not post to the ledger. Activation does not check them. 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: 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 tax regime none computes none, so the two-leg rule is the one that balances in this walkthrough. In multi-tenant mode the profile also needs midazOrganizationId and midazLedgerId. Read Define a loan product for the wider product surface.

4. Activate the product


POST /api/v1/loan-products/{productId}/activate
Activation pins the version that new applications originate on. It refuses a product that has no accounting profile, and a Brazilian version that has no carteira. An application on a product that is not active is refused.

5. Create the application


POST /api/v1/loan-applications Send the header X-Idempotency. Lender requires it on this call and on steps 6 and 7. A retry with the same value replays the first response and writes nothing new.
  • borrowerId is a UUID. borrowerKind takes pessoaFisica or pessoaJuridica, and the version must admit it.
  • 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 and compares it with the version’s rate band in annual basis points: "0.01500000" a month is 1800 bps a year, inside the band from step 2.
  • requestedInstallments is between 1 and 600.
  • previewScheduleSnapshotId is optional. To cite the quote you showed the borrower, send the scheduleOfferSnapshotId that POST /api/v1/loan-applications/preview-schedule returned.
  • 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.

6. Approve


POST /api/v1/loan-applications/{id}/approve
approvedAmount becomes the ceiling on everything you disburse. The application moves to approved and carries the decision record.

7. Disburse


POST /api/v1/loan-applications/{id}/disburse
With tax regime none, netDeliveredAmount must equal grossRequestedAmount. Lender accepts a disbursement only when net equals gross minus withholdings minus the retained origination fee. This regime computes no withholdings, and this walkthrough retains no fee.
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. jurisdictionExtensions carries what the Brazilian pipeline reads: the disbursement instant, the version from step 2, and this call’s X-Idempotency value as requestId. The disbursement fails when one is absent. Lender also checks that:
  • netDeliveredAmount does not exceed grossRequestedAmount.
  • grossRequestedAmount equals approvedAmount, because this is the application’s only disbursement. A release in tranches declares trancheNumber, and its running total does not exceed approvedAmount.
  • decisionAt and disbursedAt are not before the application’s creation, and disbursedAt is not before the approval decision.
  • The jurisdiction and profile version still match the pair Lender resolved at step 5.

Confirm the loan exists


The disburse response carries the application in disbursed with its disbursement event. Then read the loan account: 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 2. If you configured the Midaz ledger relay, the disbursement posting reaches the ledger when the outbox dispatcher relays the intent. That happens shortly after the call, not inside it. Otherwise the intent remains in the outbox.

Next steps


Service a loan

Record repayments, prepay, reschedule, and correct a live loan account.

How accrual works

Interest recognition runs on its own schedule. Start an accrual run, or enable the heartbeat.