Skip to content

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.

Terminal window
export ML_KEY="vp_REPLACE_WITH_YOUR_KEY"

Send your own order id and the price you are charging, in paise.

Terminal window
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 back list_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_url is 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 a 200. Send the same order_id at a different amount and you get a 409, because that is almost always a bug.
  • That 200 is not always as bare as the 201 above: if the existing order still holds a live reservation, the 200 carries the same upi block a fresh reservation would. If it does not — never reserved, or the reservation has since lapsed — upi is simply absent, and you call reserve (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.

Terminal window
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.

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:

Terminal window
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 of paid_paise, discount_paise, reserve_expires_at, evidence, rrn, payer_vpa, payer_name, confirmed_at.
  • After reserve, paid_paise, discount_paise and (while the reservation is still live) reserve_expires_at appear — that is what was reserved for this order, whether or not it has been paid yet. evidence, rrn, payer_vpa, payer_name and confirmed_at stay absent until status actually becomes confirmed.

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.

Terminal window
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:

Terminal window
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:

Terminal window
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.

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.