Some QR projects are one code on one poster. Others are one code per serial number, per seat, per parcel, per hotel room — thousands of them, each pointing somewhere slightly different, each needing to be repointable later. That second category is not a dashboard job. It is an API job.
Here is how it works, what the endpoints are, and where the sharp edges are at volume.
When per-unit codes are worth it
Unique codes cost the same as shared ones and buy resolution you cannot get any other way:
- Product units. Warranty registration, authenticity, reorder — with per-unit scan data that tells you when units are actually opened rather than when they shipped.
- Assets and equipment. A code per machine that opens that machine's manual, service history, or fault report form.
- Seats and rooms. Table numbers, hotel rooms, meeting rooms, parking bays — each opening a context-aware page.
- Parcels and shipments. A tracking or returns page, per consignment.
- Attendees and members. A code per badge, per membership card.
The alternative — one shared code plus a manual step where the user tells you which unit they have — loses a large fraction of users at exactly the moment they were willing to engage.
Getting a key
The REST API is available on Pro and up. In the dashboard, open API keys and create one. The key looks like qrm_ followed by a long hex string, and it is shown once — store it in your secret manager immediately.
Every request carries it as a bearer token:
Authorization: Bearer qrm_your_key_here
The base URL is https://qrmapper.com/api/v1.
The endpoints
Four operations, all JSON.
Create a code
curl -X POST https://qrmapper.com/api/v1/qr \
-H "Authorization: Bearer $QRMAPPER_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Unit SN-100482", "url": "https://example.com/register?sn=100482"}'
Responds 201 with the created code:
{
"qr_code": {
"id": "clx8s2v9k0001…",
"name": "Unit SN-100482",
"type": "URL",
"short_code": "ab12cd",
"short_url": "https://qrmap.cc/?q=ab12cd",
"destination": { "url": "https://example.com/register?sn=100482" },
"scan_count": 0,
"created_at": "2026-08-11T09:00:00.000Z"
}
}
List codes
curl https://qrmapper.com/api/v1/qr -H "Authorization: Bearer $QRMAPPER_KEY"
Returns { "qr_codes": [ … ] }.
Read, update, or delete one
curl -X PATCH https://qrmapper.com/api/v1/qr/ab12cd \
-H "Authorization: Bearer $QRMAPPER_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/warranty?sn=100482"}'
GET, PATCH, and DELETE all take either the code's id or its short_code in the path — useful when your own system stored the short code rather than the id.
A bulk creation script
The pattern that matters at volume: map your own records to codes, persist the mapping as you go, and never assume the whole run will succeed.
import { readFileSync, appendFileSync } from 'node:fs'
const KEY = process.env.QRMAPPER_KEY
const units = JSON.parse(readFileSync('units.json', 'utf8'))
async function createCode(unit) {
const res = await fetch('https://qrmapper.com/api/v1/qr', {
method: 'POST',
headers: {
Authorization: `Bearer ${KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: `Unit ${unit.serial}`,
url: `https://example.com/register?sn=${unit.serial}`,
}),
})
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`)
return (await res.json()).qr_code
}
for (const unit of units) {
try {
const qr = await createCode(unit)
// Write the mapping immediately — a crash at unit 4,000 must not
// orphan the first 3,999 codes with no record of what they are.
appendFileSync('mapping.csv', `${unit.serial},${qr.short_code},${qr.short_url}\n`)
} catch (err) {
appendFileSync('failed.csv', `${unit.serial},${err.message}\n`)
}
await new Promise((r) => setTimeout(r, 100)) // stay polite
}
Three things in there are load-bearing:
Persist the mapping as you go, not at the end. The single most expensive mistake in bulk QR work is creating four thousand codes and losing track of which is which. Codes without a mapping are printed garbage.
Record failures separately and continue. A partial run you can resume beats a run that aborts at 62%.
Pace the requests. A hundred-millisecond gap costs you six minutes on four thousand codes and avoids looking like an attack.
Plan limits are the first wall you hit
Code count is capped per plan, and the API returns 403 with a clear message when you cross it:
| Plan | Codes | Analytics history |
|---|---|---|
| Free | 5 | 7 days |
| Pro | 20 | 30 days |
| Business | 100 | 90 days |
| Enterprise | 500 | 365 days |
The API itself starts at Pro. If your project genuinely needs thousands of codes, that is an Enterprise conversation rather than a scripting problem — talk to us before you write the loop.
Getting the images
The API returns the short_url for each code, not an image. For print you generate the artwork from that URL — any QR library will do it, and generating locally is the right answer at volume anyway because you control the size, the error correction level, and the vector output your printer needs.
Two rules for bulk print: export vector, not raster, and test-scan a sample from the middle of the run, not just the first one. Print-Ready QR Codes: Size, Contrast, and Placement Rules has the sizing numbers, and How Much Can You Brand a QR Code Before It Stops Scanning covers how far styling can go before the scanner loses.
Repointing in bulk
The reason to do this through an API rather than a spreadsheet of static codes shows up a year later, when the registration flow moves.
const { qr_codes } = await (await fetch('https://qrmapper.com/api/v1/qr', {
headers: { Authorization: `Bearer ${KEY}` },
})).json()
for (const qr of qr_codes) {
const serial = qr.name.replace('Unit ', '')
await fetch(`https://qrmapper.com/api/v1/qr/${qr.short_code}`, {
method: 'PATCH',
headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ url: `https://example.com/v2/warranty?sn=${serial}` }),
})
}
Four thousand printed labels, repointed in a few minutes, with no reprint. That is the whole argument for dynamic codes, scaled — The Reprint Problem: Fixing a Printed QR Code Without Reprinting makes the same case for a single code.
Practical cautions
- Keep the key server-side. It creates, edits, and deletes codes. Never ship it to a browser or a mobile app.
- Make your job resumable. Check whether a serial already has a code before creating another; duplicates at volume are painful to unpick.
- Name codes so a human can search them.
Unit SN-100482beatsqr-4127. - Reconcile after the run. List all codes and diff against your mapping file before anything goes to print.
- Rotate the key when someone with access leaves.
The API reference lives under API docs in the dashboard, alongside your keys. Pro starts at $6 a month billed annually with a 14-day trial, and a free account is enough to explore the dashboard before you commit — the API itself begins at Pro.
Bulk QR work is a data problem wearing a printing problem's clothes. Get the mapping right, keep the destinations editable, and four thousand labels stay correct for as long as you need them to.
Print once. Point anywhere.
Create a dynamic QR code you can re-point anytime — free, no credit card.
Get started free