Reservations
An amount is reserved for one order at a time. This page is when that starts, when it ends, and what it costs you when a lot of people are paying at once.
The life of a reservation
Section titled “The life of a reservation”open ──(customer taps)──▶ reserved ──(credit lands)──▶ hold ──▶ released, confirmed reserved ──(nobody paid)──▶ released, expired ──▶ rests, out of circulation any ──(you cancel)────────▶ released, cancelled ──▶ rests, out of circulationReserved at the tap, not at creation. POST /v1/intents creates an order
and reserves nothing. Nothing is held until the customer taps a pay button, the
web SDK reports a tap, or a QR is rendered. Browsing costs nothing and an
abandoned cart costs nothing, which is the whole reason the two calls are
separate.
If you genuinely cannot call at tap time — a backend that mints a link and
hands it off with no further contact — pass reservation: "immediate" on
creation and accept that the amount is held for the whole open window instead
of for the couple of minutes somebody is actually paying.
The clock
Section titled “The clock”| default | range | what it is | |
|---|---|---|---|
| reserve | 300 s | 60–900 s | How long a reservation lives without a heartbeat. |
| extension cap | 900 s | — | A reservation can never outlive 15 minutes from when the amount was first taken, however many heartbeats arrive. |
| hold | 180 s | 60–900 s | After the credit lands. Absorbs the second alert of a double payment, and the second feed’s copy of the same credit. |
| rest | 3600 s | 3600–7200 s | How long an amount released unpaid — expired or cancelled — is passed over, while any other free amount exists. Never configurable below the default; that floor is enforced, not a suggestion. |
| order ttl | 86400 s | 1–604800 s | The order’s own lifetime, set per order with ttl. |
The first four are per-tenant, in Settings. The last is per order.
Rest is the setting that makes small sellers work. Without it, an amount
abandoned by a slow payer goes straight back into circulation and the next
customer gets it — so when the slow payer finally pays, two orders are holding
the same amount and the credit is ambiguous. Resting it for an hour by default
means the late payment almost always still belongs to exactly one order, which
is what makes the late evidence label trustworthy. See
Evidence.
Keeping it alive
Section titled “Keeping it alive”A visible checkout should heartbeat:
curl -sS -X POST https://moneylanded.com/v1/intents/int_9f2c1a77b40e6d3a5c81/extend \ -H "Authorization: Bearer $ML_KEY"{ "reserve_expires_at": 1758211620 }A 409 with {"error":"no open reservation"} means it already lapsed. Call
reserve again — and re-render your buttons from the new response, because the
amount may have changed.
Our own web SDK polls status every 2 seconds and heartbeats every 60, which is
31 requests a minute against a 60-a-minute limit on the page token. If you are
writing your own front end against /p/{token}/*, stay inside that.
What it costs at volume
Section titled “What it costs at volume”One price point, Poisson arrivals, reservations at the five-minute default
(reserve, above). “Open at once” is roughly orders-per-hour × the reserve
window; “max discount” is what the last concurrent customer sees — the
average is far lower, because the walk starts at the list price and most
customers take the top of it.
| orders per hour | open at once | max discount | max discount, per-app pools | a 2% gateway on ₹499 |
|---|---|---|---|---|
| 100 | ~8 | ₹0.08 | ₹0.03 | ₹9.98 |
| 1,000 | ~83 | ₹0.83 | ₹0.28 | ₹9.98 |
| 3,000 | ~250 | ₹2.50 | ₹0.83 | ₹9.98 |
| 10,000 | ~833 | ₹8.33 | ₹2.78 | ₹9.98 |
This counts open and held reservations only. It does not add amounts that
are currently resting (above), which also shrink the free pool for as long as
they rest. Treat it as a floor, not a ceiling: the band you actually need to
stay clear of pool_exhausted can run deeper than these numbers, especially
once real-world abandonment is in the mix. Your band has a hard ceiling either
way — ₹9.99 (999 paise), tenant-configurable up to that and no further — and
the 10,000-an-hour row is close enough to it, and understated enough by the
gap above, that per-app pools or a bank merchant VPA stop being optional at
that volume.
The right-hand column is what a payment gateway charges to do the correlation for you, on the same order, at every volume. It does not move.
If your volume needs a deeper band than you are willing to give away, widening the band is the wrong answer. Get a merchant VPA from your own bank: that channel returns your order reference directly and removes amount variation completely. Money still never touches us. It is not built yet.
When there is nothing left
Section titled “When there is nothing left”{ "error": "pool_exhausted", "retry_after_seconds": 30 }A 503. Every amount from your list price down to the bottom of your band is
held right now. We refuse rather than hand out an amount that cannot be told
apart from another order — an ambiguous amount is worse than a retry. We alert
our operators at the same time, and the hosted pay page says “busy, try again
in a moment”.
Retry after retry_after_seconds. Do not widen the amount yourself.
Per-app pools
Section titled “Per-app pools”Off by default, and only worth turning on at thousands of orders an hour.
The payer’s UPI handle suffix names the app they paid from, exactly:
@ybl, @ibl, @axl is PhonePe; @okaxis, @okhdfcbank, @okicici,
@oksbi is Google Pay; @paytm and its siblings are Paytm; @upi is BHIM.
With pools on, a tap on your PhonePe button reserves in the PhonePe pool, so the same amount can be live once per app and your effective capacity roughly triples — the fourth column of the table above.
The catch, and the reason it is opt-in: a customer taps PhonePe and then pays from Google Pay. Now the credit’s handle says Google Pay, and a different order is holding that amount in the Google Pay pool. Both are candidates, so the credit is parked for you. At low volume that trade is not worth making.
A QR render and the generic “other UPI app” button reserve in the any pool,
which blocks every pool.
Cancelling
Section titled “Cancelling”curl -sS -X POST https://moneylanded.com/v1/intents/int_9f2c1a77b40e6d3a5c81/cancel \ -H "Authorization: Bearer $ML_KEY"Releases the reservation immediately — nothing is held for this order from that moment on — but the amount itself rests, exactly like an unpaid expiry. A customer can still pay a cancelled order late, from a stale screen, so the amount is not handed to the next customer until rest has run its course.
That also means a late payment against a cancelled order never auto-confirms
it: the late evidence label only reaches an expired reservation, never a
cancelled one. The credit parks instead, with the cancelled order listed
among its candidates, for you to place by hand if you decide to honour it.
Cancelling a cancelled order is a 200. Cancelling a confirmed order is a
409 — the money is already in your account and there is nothing here to
undo. Refunding it is between you and your customer’s bank, and no part of it
involves us.