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_iduuid✅The buyer
regionstring✅Contract region
asset_descriptionstring✅What is being sold
purchase_pricestring✅Minor units — what the seller pays the supplier
markup_rateinteger✅Basis points, 1–10000. 1500 is 15 %
payment_monthsinteger✅1–360 instalment periods of 30 days
asset_typeenum—vehicle | real_estate | equipment | commodity | inventory
currencystring—ISO 4217
down_paymentstring—Minor units. Must be less than the sale price
asset_specificationsobject—Free-form specification
supplier_name, supplier_contactstring—May also be supplied at order_asset
product_iduuid—Product template
metadataobject—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_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_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_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_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.