Skip to main content

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
StatusMeaning
draftTerms recorded, nothing has moved
under_reviewSubmitted for approval
review_passedA conclusion is attached
rejectedTerminal. Requires a reason
asset_orderedOrdered from the supplier
asset_purchasedThe seller owns the asset
deliveredHanded to the buyer; title has passed
activeInstalments are running
completedPaid in full
defaultedWritten 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"
}
FieldTypeRequiredNotes
customer_iduuidThe buyer
regionstringContract region
asset_descriptionstringWhat is being sold
purchase_pricestringMinor units — what the seller pays the supplier
markup_rateintegerBasis points, 1–10000. 1500 is 15 %
payment_monthsinteger1–360 instalment periods of 30 days
asset_typeenumvehicle | real_estate | equipment | commodity | inventory
currencystringISO 4217
down_paymentstringMinor units. Must be less than the sale price
asset_specificationsobjectFree-form specification
supplier_name, supplier_contactstringMay also be supplied at order_asset
product_iduuidProduct template
metadataobjectReturned 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_price with a low markup_rate can 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_passedasset_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_purchaseddelivered.

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. deliveredactive.


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_payment is floored, so the last payment is larger by whatever the division dropped. The schedule adds up to financed_amount exactly.

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 parameterValues
statusany status above
regioncontract region
asset_typevehicle | real_estate | equipment | commodity | inventory
customer_iduuid
envtest | 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_reviewrejected 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.