Instalment Sales
The seller buys the asset the buyer wants, takes ownership of it, and resells it to the buyer at a disclosed markup payable over time.
The markup is fixed once, at signing. It does not grow with time, and it does not grow with delay. A buyer 90 days late owes exactly what a buyer paying on schedule owes — the delinquency is tracked, but nothing is added to the debt for it. That single property is what separates this product from a loan: the seller's return comes from a trade in a real asset, not from the passage of time.
The consequence for your integration is a sequence you cannot skip: the seller must actually acquire the asset before selling it. The API has four transitions for that sequence and enforces their order.
Base path: /api/v1/pfp/installment-sales · Religious reading: Murabaha
Statuses
draft ──submit──> under_review ──review──> review_passed
│ │ │
│ └──reject──> rejected │
│ │
└──────────────┬──────────────────────────────┘
│
order_asset│ (optional)
v
asset_ordered
│
purchase_asset │ ← also reachable directly from draft / review_passed
v
asset_purchased
│ deliver_asset
v
delivered
│ activate
v
active ──> completed
└──> defaulted
| Status | Meaning |
|---|---|
draft | Terms recorded, nothing has moved |
under_review | Submitted for approval |
review_passed | A conclusion is attached |
rejected | Terminal. Requires a reason |
asset_ordered | Ordered from the supplier |
asset_purchased | The seller owns the asset |
delivered | Handed to the buyer; title has passed |
active | Instalments are running |
completed | Paid in full |
defaulted | Written off |
Create a contract
POST /api/v1/pfp/installment-sales
{
"customer_id": "8f2a1c44-0b6e-4d51-9a3f-1c8e5b7d2a90",
"region": "RU",
"currency": "RUB",
"asset_type": "vehicle",
"asset_description": "Lada Vesta SW Cross, 2026",
"purchase_price": "180000000",
"markup_rate": 1500,
"payment_months": 36,
"down_payment": "36000000",
"supplier_name": "AvtoDom Kazan",
"supplier_contact": "+7 843 000-00-00"
}
| Field | Type | Required | Notes |
|---|---|---|---|
customer_id | uuid | ✅ | The buyer |
region | string | ✅ | Contract region |
asset_description | string | ✅ | What is being sold |
purchase_price | string | ✅ | Minor units — what the seller pays the supplier |
markup_rate | integer | ✅ | Basis points, 1–10000. 1500 is 15 % |
payment_months | integer | ✅ | 1–360 instalment periods of 30 days |
asset_type | enum | — | vehicle | real_estate | equipment | commodity | inventory |
currency | string | — | ISO 4217 |
down_payment | string | — | Minor units. Must be less than the sale price |
asset_specifications | object | — | Free-form specification |
supplier_name, supplier_contact | string | — | May also be supplied at order_asset |
product_id | uuid | — | Product template |
metadata | object | — | Returned untouched |
What the platform derives
markup_amount = floor(purchase_price × markup_rate / 10000)
sale_price = purchase_price + markup_amount
financed_amount = sale_price − down_payment
monthly_payment = floor(financed_amount / payment_months)
For the request above: markup 270 000 RUB, sale price 2 070 000 RUB, financed 1 710 000 RUB, monthly 47 500 RUB.
Two rejections to expect:
- Markup that floors to zero. A small
purchase_pricewith a lowmarkup_ratecan produce a markup of zero minor units. The contract is refused — a sale at cost is an interest-free loan, not a sale, and it would pass the "rate above zero" check while selling at the purchase price. Raise the rate or the price. - Down payment at or above the sale price. Refused: at equal there is nothing to finance, above it the schedule would have the seller paying the buyer.
The acquisition sequence
The seller cannot sell what it does not own. Four transitions, in order.
1. Order from the supplier (optional)
POST /api/v1/pfp/installment-sales/{id}/order_asset
{ "supplier_name": "AvtoDom Kazan", "supplier_contact": "+7 843 000-00-00" }
Both fields are optional here if they were given at creation. draft or review_passed → asset_ordered.
This step can be skipped: purchase_asset accepts a contract that is still at draft or review_passed. Use it when you track the supplier order as a distinct event, omit it when you do not.
2. The seller buys it
POST /api/v1/pfp/installment-sales/{id}/purchase_asset
{
"asset_purchase_date": "2026-09-02",
"ownership_documents": ["invoice-4471.pdf", "title-transfer.pdf"]
}
asset_purchase_date is required. Reachable from asset_ordered, and also directly from draft or review_passed if you skipped the order step.
This is the transition that makes the product what it is. The seller must own the asset, if only for a moment, before reselling it. A contract that goes from approval straight to instalments without this step is a loan.
3. Hand it over
POST /api/v1/pfp/installment-sales/{id}/deliver_asset
{
"delivery_date": "2026-09-05",
"ownership_transfer_date": "2026-09-05"
}
Both dates are required and recorded separately: physical delivery and the passing of title are not always the same day, and which one governs depends on the jurisdiction. asset_purchased → delivered.
4. Start the instalments
POST /api/v1/pfp/installment-sales/{id}/activate
{ "first_payment_date": "2026-10-05" }
Optional; defaults to today. This is where the schedule is generated. delivered → active.
The payment schedule
Generated at activation into payment_schedule, one entry per period:
{
"payment_schedule": [
{
"payment_number": 1,
"due_date": "2026-10-05",
"amount": "4750000",
"remaining_balance": "166250000"
},
{
"payment_number": 2,
"due_date": "2026-11-04",
"amount": "4750000",
"remaining_balance": "161500000"
}
]
}
Two things to know about it:
- Periods are 30 days, not calendar months. Due dates drift relative to the calendar: a schedule starting 5 October has its second payment on 4 November. This is deliberate and consistent across the whole product family.
- The rounding remainder is added to the final instalment.
monthly_paymentis floored, so the last payment is larger by whatever the division dropped. The schedule adds up tofinanced_amountexactly.
maturity_date is the first payment date plus (payment_months − 1) × 30 days — the date of the last instalment, not a month after it.
Register a payment
POST /api/v1/pfp/installment-sales/{id}/payment
{ "amount": "4750000" }
Reduces outstanding_amount, increases total_paid and payments_made, sets last_payment_date. When the outstanding balance reaches zero the contract moves to completed.
Overpayment is capped at the outstanding balance. Sending more than is owed applies only what remains; the excess is not recorded as paid. Refund it through your own settlement, not through this endpoint — otherwise "paid" stops reconciling against "financed".
Delinquency
POST /api/v1/pfp/installment-sales/{id}/delinquency
{ "days_past_due": 45, "overdue_amount": "9500000" }
days_past_due is required and may be 0 to clear the delinquency. overdue_amount is optional.
This records the delinquency; it does not change what is owed. There is no late fee, no penalty accrual, and no re-pricing of the markup. If your jurisdiction permits a late charge that goes to charity rather than to the seller, handle it outside this contract.
List and read
GET /api/v1/pfp/installment-sales
| Query parameter | Values |
|---|---|
status | any status above |
region | contract region |
asset_type | vehicle | real_estate | equipment | commodity | inventory |
customer_id | uuid |
env | test | prod |
GET /api/v1/pfp/installment-sales/{id}
The detail response adds the schedule, the supplier, the ownership documents and dates, the review, and the money breakdown:
{
"data": {
"id": "c41d8b90-3e77-4a12-b5f6-9e2d7a1c0843",
"contract_number": "INS-RU-20260823-9E4B02",
"status": "active",
"asset_type": "vehicle",
"asset_description": "Lada Vesta SW Cross, 2026",
"purchase_price": "180000000",
"markup_rate": 1500,
"markup_amount": "27000000",
"sale_price": "207000000",
"down_payment": "36000000",
"financed_amount": "171000000",
"monthly_payment": "4750000",
"outstanding_amount": "166250000",
"total_paid": "4750000",
"payments_made": 1,
"payment_months": 36,
"days_past_due": 0,
"overdue_amount": null,
"asset_purchase_date": "2026-09-02",
"delivery_date": "2026-09-05",
"ownership_transfer_date": "2026-09-05",
"first_payment_date": "2026-10-05",
"maturity_date": "2029-08-27",
"compliance_review": null
}
}
markup_rate is the one number in that response that is not a monetary amount and therefore not a string: it is basis points.
Approval and rejection
POST /api/v1/pfp/installment-sales/{id}/submit
POST /api/v1/pfp/installment-sales/{id}/review
POST /api/v1/pfp/installment-sales/{id}/reject
submit takes no body. review takes the approval record — approved_by required, signed_at, document_ref and note optional. reject requires a reason and is terminal.
reject moves under_review → rejected and is terminal. In the secular reading review is optional: order_asset and purchase_asset are both reachable straight from draft, so a contract that never goes through review can still complete the full sequence.