APIs
QR Code API
Accept payments in person. Mint static QR codes to print, or dynamic ones per transaction with the amount fixed.
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:
| Static | Dynamic | |
|---|---|---|
| Amount | the payer types it | you fix it |
| Lifetime | until you deactivate it | minutes |
| Image | we render and host a PNG | you render the payload |
| Made for | the standee on your counter | one 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-codesSend an Idempotency-Key so a retried create replays the first response instead of minting a second code.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
outlet_id | integer | Yes | The outlet to attribute payments to. Must be one of yours. |
type | string | No | STATIC (default) or DYNAMIC. |
amount | integer | For DYNAMIC | Amount in laari (100 laari = 1 MVR). Rejected for a static code, where the payer enters it. |
expires_in_minutes | integer | No | Dynamic only. Defaults to, and is capped at, the scheme maximum. |
title | string | No | Shown to the payer. Defaults to the outlet name. Max 255 characters. |
description | string | No | Extra line under the title. Max 255 characters. |
reference | string | No | Your own identifier. You can fetch the code by it later. |
A static code for the counter
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"
}'{
"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
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"
}'{
"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:
import QRCode from "qrcode";
const { payload } = await createQrCode({ amount: 25050 });
await QRCode.toCanvas(document.getElementById("qr"), payload, { width: 320 });Errors
| Status | Meaning |
|---|---|
401 | Missing or invalid key. |
403 | The key does not hold qr:write. |
404 | outlet_id is unknown, or belongs to another merchant. |
422 | A dynamic code without an amount, a static code with one, or an expiry outside the allowed range. |
503 | The image could not be rendered or stored (static only). Retry. |
Fetch a code
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.
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?outlet_id=42&type=DYNAMIC&limit=25&offset=0Newest first. limit defaults to 25 and is capped at 100. All four parameters are optional.
Deactivate a code
POST /qr-codes/{id}/deactivateStops 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.
{
"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.