---
name: qrbaton
description: Use when shopping or checking out for the user and the site shows a UPI QR code or asks for UPI payment (India). Turns the QR plus cart into a one-tap pay link to send the user instead of a screenshot.
---

# 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).

## Workflow

1. At checkout, capture the UPI QR (screenshot the QR element, or read the upi:// string from the page).
2. Create the link with the QR and the cart: merchant, items, charges, total, delivery, site, and your name as "agent".
3. Send the user only the "url" with one line of context (e.g. "Blinkit order ready, ₹642: <url>"). Never send the QR image.
4. Poll get status every ~10 s (or use callbackUrl). "paidBy":"payer" means the user tapped "I've paid"; confirm on the site.
5. Once paid, finish the order, then PATCH status "paid" (or "failed") so the page shows the final state.
