# qrbaton > Make a polished UPI pay page from a QR code or UPI details, optionally with the cart (items, charges, delivery, shop site). Send the link to whoever pays: they review the order and tap Pay to open GPay, PhonePe, Paytm or any UPI app. No API key needed. - MCP (streamable HTTP): https://qrbaton.vercel.app/mcp (tools: create_pay_link, get_payment_status, set_payment_status) - OpenAPI: https://qrbaton.vercel.app/openapi.json - Agent skill: https://qrbaton.vercel.app/SKILL.md - Live demo page: https://qrbaton.vercel.app/demo ## Create a link POST https://qrbaton.vercel.app/api/sessions (JSON). Give the payee one of three ways: - "qr": the QR image as base64 or a data: URI - "upi": the raw "upi://pay?pa=…&pn=…&am=…" string - "payee": {"pa":"shop@ybl","pn":"Shop","am":349}; these fields also override what the QR/upi says Optional "order": {"merchant","site","agent","note","total","items":[{"name","qty","price","mrp","unit","image"}],"charges":[{"label","amount"}],"delivery":{"slot","address":{"label","text"}},"orderId","logo"}. "charges" is the bill exactly as shown, top to bottom (e.g. Item total, Delivery, Handling fee); negative amounts are discounts. Amount comes from payee.am, else order.total, else the sum of items (only when there are no charges). If you send charges, send total too. If both am and total are given they must match. "ttlSec": 60 to 86400 (default 1800). "callbackUrl": optional https webhook. ```bash curl -X POST https://qrbaton.vercel.app/api/sessions -H 'content-type: application/json' -d '{ "qr": "'"$(base64 -w0 qr.png)"'", "order": {"merchant":"Blinkit","site":"https://blinkit.com","agent":"Claude","note":"Groceries for the week", "items":[{"name":"Amul Taaza Milk","unit":"1 L","qty":2,"price":56}], "charges":[{"label":"Item total","amount":112},{"label":"Delivery","amount":0}],"total":112} }' # or upload the file directly: curl -X POST https://qrbaton.vercel.app/api/sessions -F qr=@qr.png -F 'payee={"am":112}' ``` Returns {"id","url","statusUrl","manageToken","expiresAt","payee"}. Send "url" to the payer. Keep "manageToken" private. ## Status - GET https://qrbaton.vercel.app/api/sessions/{id} → {"status":"pending|paid|failed|expired","paidBy"?,"utr"?} - "paidBy":"payer" means the payer tapped "I've paid" on the page (unverified, may include their UTR). Confirm it, or overrule with failed. - PATCH https://qrbaton.vercel.app/api/sessions/{id} with "Authorization: Bearer {manageToken}" and {"status":"paid"|"failed"|"expired","utr"?}. The open page updates live. Otherwise terminal statuses are final. - Webhooks: add "callbackUrl":"https://…" when creating. Each status change POSTs {"event":"session.status","id","status","paidBy","utr",…} with header x-qrbaton-signature: sha256=HMAC-SHA256(key = hex sha256 of your manageToken, raw body). ## Notes - Limit: 60 links per hour per IP. - qrbaton never handles money. The payment happens in the payer's UPI app, which shows the real payee name. - Pages are noindex and not cached, but anyone with the link can see the order (including any delivery address).