Browse documentation

APIs

QR Code API

Accept payments in person. Mint static QR codes to print, or dynamic ones per transaction with the amount fixed.

View as Markdown

The QR Code API mints PayMV codes — the Maldives National QR — for your outlets. A customer scans one in the Payer app and pays you; the money lands in your merchant wallet like any other payment.

There are two kinds, and the difference is who decides the amount:

StaticDynamic
Amountthe payer types ityou fix it
Lifetimeuntil you deactivate itminutes
Imagewe render and host a PNGyou render the payload
Made forthe standee on your counterone per order, at a POS or checkout

All endpoints live under /qr-codes and authenticate with a secret key holding the qr:write scope. A publishable key cannot mint QR codes: a code commits you to accepting money at it, so it is a server-side operation.

Create a code

POST /qr-codes
POST /qr-codes

Send an Idempotency-Key so a retried create replays the first response instead of minting a second code.

Body parameters

FieldTypeRequiredDescription
outlet_idintegerYesThe outlet to attribute payments to. Must be one of yours.
typestringNoSTATIC (default) or DYNAMIC.
amountintegerFor DYNAMICAmount in laari (100 laari = 1 MVR). Rejected for a static code, where the payer enters it.
expires_in_minutesintegerNoDynamic only. Defaults to, and is capped at, the scheme maximum.
titlestringNoShown to the payer. Defaults to the outlet name. Max 255 characters.
descriptionstringNoExtra line under the title. Max 255 characters.
referencestringNoYour own identifier. You can fetch the code by it later.

A static code for the counter

Request
curl https://api.payer.app/qr-codes \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: counter_1" \
  -H "Content-Type: application/json" \
  -d '{
    "outlet_id": 42,
    "title": "Counter 1",
    "reference": "counter_1"
  }'
201 Created
{
  "id": "5b3f2a1e-7c48-4d9a-9f60-2e8b1c4d7a91",
  "type": "STATIC",
  "status": "ACTIVE",
  "outletId": "42",
  "title": "Counter 1",
  "description": null,
  "amount": null,
  "payload": "00020101021135660014mv.favara.mpqr0108PAYRMVMV...6304F8EA",
  "qrid": "QR3EC6E56A",
  "imageUrl": "https://payer.sgp1.cdn.digitaloceanspaces.com/qr_codes/12/5b3f2a1e-....png",
  "reference": "counter_1",
  "expiresAt": null,
  "createdAt": "2026-07-31T09:14:22+00:00"
}

Print imageUrl, or render payload yourself if you want it in your own artwork. Create the code once and reuse it — you do not need a new one per customer.

A dynamic code for an order

Request
curl https://api.payer.app/qr-codes \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: order_10482" \
  -H "Content-Type: application/json" \
  -d '{
    "outlet_id": 42,
    "type": "DYNAMIC",
    "amount": 25050,
    "expires_in_minutes": 5,
    "reference": "order_10482"
  }'
201 Created
{
  "id": "0d0a4e6c-...",
  "type": "DYNAMIC",
  "status": "ACTIVE",
  "amount": 25050,
  "payload": "00020101021235660014mv.favara.mpqr...6304A1B2",
  "qrid": "QR8F21C0DE",
  "imageUrl": null,
  "reference": "order_10482",
  "expiresAt": "2026-07-31T09:19:22+00:00"
}

The payer is charged exactly amount — a payment for any other value is rejected — and the code stops working at expiresAt, including between the customer opening the app and confirming.

Note

A dynamic code has no imageUrl. It lives for minutes, so hosting an image for each one would be wasted work on both sides. Encode the payload string with any QR library — that string is the whole QR content, nothing else goes into it.

Rendering the payload, for example in JavaScript:

Render the payload
import QRCode from "qrcode";

const { payload } = await createQrCode({ amount: 25050 });
await QRCode.toCanvas(document.getElementById("qr"), payload, { width: 320 });

Errors

StatusMeaning
401Missing or invalid key.
403The key does not hold qr:write.
404outlet_id is unknown, or belongs to another merchant.
422A dynamic code without an amount, a static code with one, or an expiry outside the allowed range.
503The image could not be rendered or stored (static only). Retry.

Fetch a code

GET /qr-codes/{id}
GET /qr-codes/{id}

{id} is the id we returned or your own reference, so you can look a code up by the order number you already have.

Request
curl https://api.payer.app/qr-codes/order_10482 \
  -H "Authorization: Bearer sk_test_..."

Codes belonging to another merchant return 404, the same as ones that do not exist.

List codes

GET /qr-codes
GET /qr-codes?outlet_id=42&type=DYNAMIC&limit=25&offset=0

Newest first. limit defaults to 25 and is capped at 100. All four parameters are optional.

Deactivate a code

POST /qr-codes/{id}/deactivate
POST /qr-codes/{id}/deactivate

Stops a code being payable — use it when a standee is retired or an order is cancelled. Send an Idempotency-Key so a retried call is safe.

200 OK
{
  "id": "5b3f2a1e-...",
  "status": "INACTIVE",
  "type": "STATIC"
}

The code is kept rather than deleted: a printed code is part of your payment history, and a customer who scans it afterwards is told it is no longer active. Deactivating twice returns 400.

Reconciling payments

Every code carries a QRID (qrid) — the identifier the Maldives National QR standard uses to tie a code to the payment it produced. It is recorded against the resulting transaction, so it is the field to reconcile on. Pair it with your own reference to trace a payment back to the order that created the code.

Displaying a code

The standard sets out how a PayMV code must be presented — issuer logo, merchant name, the QRID, and the "Maldives National QR" band, in the scheme's blue. We return the payload and, for static codes, a plain PNG; the surrounding card is yours to produce for now. Follow the PayMV branding rules when you design it.