Quickstart
Four calls. Copy them as they are; the only things you have to change are your API key and your own order id.
Your API key is shown once, right when you finish signing up. It looks like
vp_ followed by 64 hex characters, and we store only a hash of it — if you
lose it, Settings in the dashboard can rotate you a new one, but it can
never show you the one you already have.
export ML_KEY="vp_REPLACE_WITH_YOUR_KEY"1. Create the order
Section titled “1. Create the order”Send your own order id and the price you are charging, in paise.
curl -sS https://moneylanded.com/v1/intents \ -H "Authorization: Bearer $ML_KEY" \ -H "Content-Type: application/json" \ -d '{"order_id":"ORD-10432","amount_paise":49900,"description":"Blue mug"}'{ "intent_id": "int_9f2c1a77b40e6d3a5c81", "status": "open", "list_paise": 49900, "page_url": "https://moneylanded.com/p/4f0a9c2b7e1d8536a0b4c9de1f327a65", "expires_at": 1758297600, "page_token": "4f0a9c2b7e1d8536a0b4c9de1f327a65"}Nothing is reserved yet. An order that is browsed and abandoned costs you nothing, which is why the next call is separate from this one.
Four things worth noticing:
- You sent
amount_paise; you got backlist_paise. Same number, different name, because from here on “the amount” splits in two: the price you asked for and the amount that actually gets paid. page_urlis a complete, ready-to-send pay page. If you are an Instagram or WhatsApp seller, you are already done — send that link and skip to step 4.- This call is idempotent on
order_id. Retry it after a timeout and you get the same order back with a200. Send the sameorder_idat a different amount and you get a409, because that is almost always a bug. - That
200is not always as bare as the201above: if the existing order still holds a live reservation, the200carries the sameupiblock a fresh reservation would. If it does not — never reserved, or the reservation has since lapsed —upiis simply absent, and you callreserve(step 2) as normal.
2. Reserve the amount, when the customer taps
Section titled “2. Reserve the amount, when the customer taps”Call this the moment the customer commits — they tapped Pay, not when the page loaded.
curl -sS -X POST https://moneylanded.com/v1/intents/int_9f2c1a77b40e6d3a5c81/reserve \ -H "Authorization: Bearer $ML_KEY" \ -H "Content-Type: application/json" \ -d '{"app":"phonepe"}'{ "paid_paise": 49899, "discount_paise": 1, "reserve_expires_at": 1758211500, "upi_uri": "upi://pay?pa=bluemug@okhdfcbank&pn=Blue%20Mug%20Co&am=498.99&cu=INR&tn=Blue%20mug&tr=int_9f2c1a77b40e6d3a5c81", "app_links": { "phonepe": "phonepe://pay?pa=bluemug@okhdfcbank&pn=Blue%20Mug%20Co&am=498.99&cu=INR&tn=Blue%20mug&tr=int_9f2c1a77b40e6d3a5c81", "gpay": "gpay://upi/pay?pa=bluemug@okhdfcbank&pn=Blue%20Mug%20Co&am=498.99&cu=INR&tn=Blue%20mug&tr=int_9f2c1a77b40e6d3a5c81", "paytm": "paytmmp://pay?pa=bluemug@okhdfcbank&pn=Blue%20Mug%20Co&am=498.99&cu=INR&tn=Blue%20mug&tr=int_9f2c1a77b40e6d3a5c81" }, "vpa": "bluemug@okhdfcbank", "qr_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" ...>"}paid_paise is 49899, one paisa below the ₹499 you asked for, because
somebody else was already holding ₹499.00 at that moment. discount_paise is
how much you gave up: one paisa. It is never negative and paid_paise is never
above list_paise — this is a discount, never a surcharge.
Why amounts differ by paise explains how to put that on the
invoice.
Open upi_uri on Android, or the matching entry in app_links on iOS, where
there is no system-wide UPI chooser. On a desktop, render qr_svg.
Calling reserve again while the reservation is still open returns the same
amount and the same expiry. Once it lapses, calling again may hand you a
different amount — so render the links from the response you just got rather
than caching them anywhere.
If every amount in your discount band is held right now you get a 503 with
{"error":"pool_exhausted","retry_after_seconds":30}. Retry. Do not pick your
own amount; an amount we did not reserve cannot be confirmed.
3. Wait for the money
Section titled “3. Wait for the money”The customer pays. Their bank moves the money to your bank. Your bank sends you its usual credit alert, that alert reaches us — by mail forwarding, by the reporter app on your phone, or both — and we match it on the amount.
Poll the order:
curl -sS https://moneylanded.com/v1/intents/int_9f2c1a77b40e6d3a5c81 \ -H "Authorization: Bearer $ML_KEY"{ "intent_id": "int_9f2c1a77b40e6d3a5c81", "status": "confirmed", "list_paise": 49900, "page_url": "https://moneylanded.com/p/4f0a9c2b7e1d8536a0b4c9de1f327a65", "expires_at": 1758297600, "paid_paise": 49899, "discount_paise": 1, "evidence": "amount", "rrn": "530112345678", "payer_vpa": "asha@ybl", "payer_name": "ASHA KUMARI", "confirmed_at": 1758211343}status is confirmed and evidence is amount — the money landed on an
amount only this order was holding. Evidence covers the
other three labels and what each one is worth.
payer_vpa and payer_name are your receipt. They played no part in deciding
this was your order, and they never will.
Polling is fine and fully supported. If you would rather be told, point a
webhook at your server and take payment.confirmed. Do
both if you like; they are the same decision reported twice.
Two different moments, two different sets of fields, and neither of them is ever present-and-null — check for the key, not for a null:
- Before you call
reserve(step 2), the object carries none ofpaid_paise,discount_paise,reserve_expires_at,evidence,rrn,payer_vpa,payer_name,confirmed_at. - After
reserve,paid_paise,discount_paiseand (while the reservation is still live)reserve_expires_atappear — that is what was reserved for this order, whether or not it has been paid yet.evidence,rrn,payer_vpa,payer_nameandconfirmed_atstay absent untilstatusactually becomesconfirmed.
4. Look at what did not land cleanly
Section titled “4. Look at what did not land cleanly”This is the call people skip and then wonder about. Your bank account receives money that has nothing to do with orders — refunds, family, a salary — and customers do things we cannot place with certainty. None of it is guessed at, dropped, or turned into an error. It is parked here.
curl -sS "https://moneylanded.com/v1/credits?status=unplaced,ambiguous,duplicate,stale,late" \ -H "Authorization: Bearer $ML_KEY"{ "credits": [ { "rrn": "530198765432", "amount_paise": 50000, "payer_vpa": "asha@ybl", "payer_name": "ASHA KUMARI", "source": "email", "credited_at": 1758212001, "received_at": 1758212042, "status": "unplaced", "matched_intent": null, "candidates": ["int_9f2c1a77b40e6d3a5c81"], "note": "no reservation at this amount" } ], "cursor": null}cursor is always present, never omitted — null here because this is the
last (and only) page. Pass it back as ?cursor=... to get the next one.
₹500.00 landed while an order was holding ₹498.99 — an iPhone customer used the copy-the-UPI-ID fallback and rounded the amount up by hand. We will not assume that was for this order, so it is parked with the order listed as a candidate.
Place it:
curl -sS -X POST https://moneylanded.com/v1/intents/int_9f2c1a77b40e6d3a5c81/attribute \ -H "Authorization: Bearer $ML_KEY" \ -H "Content-Type: application/json" \ -d '{"rrn":"530198765432"}'{ "intent_id": "int_9f2c1a77b40e6d3a5c81", "status": "confirmed", "rrn": "530198765432", "paid_paise": 50000, "delta_paise": 101}delta_paise is ₹1.01 more than was reserved, recorded so your books can
show it. Evidence becomes manual.
Or, if it was never a payment for anything:
curl -sS -X POST https://moneylanded.com/v1/credits/530198765432/dismiss \ -H "Authorization: Bearer $ML_KEY"The row is kept for audit; it just stops asking you about it.
You never ask the customer for that RRN. It is the reference on your bank’s alert, not the one their app shows them, and the two are not always the same number. There is a one-click version of both of these in the dashboard’s Unplaced payments screen.
The short version for sellers
Section titled “The short version for sellers”If you do not have a backend, steps 1 and 2 happen for you. Create a link in the dashboard, or a reusable product link for an Instagram bio, send it, and the page reserves the amount when the buyer taps. Two buyers opening the same product link at the same moment each get their own order and their own amount.
- Why amounts differ by paise — and how to invoice it.
- Reservations — how long an amount is held, and what happens at volume.
- Evidence — what a confirmation proves.
- Webhooks — and how to verify the signature.
- API reference — everything else.
- Limits — read this before you ship.