Guide
Create a digital business card and QR code from scratch
One continuous example. You start with an API key and finish by scanning a QR code that opens a live card. Each step is shown on its own, and the complete script is at the end.
Check the key
GET /me
Create the card
POST /cards
Read it back
GET /cards/:id
Download the QR
GET /qr-codes/:id/image
Scan it
The card opens
Prerequisites
- A QRBold account on the Pro plan or the Business card plan.
- An API key with
cards:readandcards:write, from Settings → Developer API. - Room for one more card on your plan.
- Node.js 20 or later for the complete script. The individual steps also show cURL and Python.
- A phone with a camera.
Set up authentication
Put the key in an environment variable so it never appears in a file:
export QRBOLD_API_KEY="qrb_live_your_key_here"$env:QRBOLD_API_KEY = "qrb_live_your_key_here"Confirm it works before going further:
curl "https://api.qrbold.com/api/public/v1/me" \
-H "Authorization: Bearer $QRBOLD_API_KEY"A 200 with your key’s name means you are ready. Anything else: see troubleshooting.
Create the card
The card is for a fictional person, Ada Lovelace. No template is used, so the card gets the default design and no field is restricted. No shortCode is sent either, so the request cannot fail because a slug is taken.
curl -X POST "https://api.qrbold.com/api/public/v1/cards" \
-H "Authorization: Bearer $QRBOLD_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"fields": {
"firstName": "Ada",
"lastName": "Lovelace",
"title": "Head of Engineering",
"company": "Analytical Engines Ltd",
"photoUrl": "https://example.com/photos/ada.jpg",
"companyLogoUrl": "https://example.com/brand/logo.png",
"bio": "Builds calculating machines and the teams around them.",
"emails": [
{
"label": "Work",
"value": "ada@example.com"
}
],
"phones": [
{
"label": "Mobile",
"value": "+44 20 7946 0958"
}
],
"links": [
{
"type": "website",
"url": "example.com"
},
{
"type": "linkedin",
"url": "linkedin.com/in/ada-lovelace"
}
],
"address": {
"city": "London",
"country": "United Kingdom"
}
}
}'The response is 201 with the card. The shortCode was generated for you:
{
"object": "card",
"id": "cmf3k2a9x0001",
"name": "Ada Lovelace",
"shortCode": "k3Vq8ZtB",
"status": "published",
"url": "https://qrbold.com/c/k3Vq8ZtB",
"vcardUrl": "https://qrbold.com/c/k3Vq8ZtB/vcard",
"templateId": null,
"externalId": null,
"paused": false,
"fields": {
"firstName": "Ada",
"lastName": "Lovelace",
"title": "Head of Engineering",
"company": "Analytical Engines Ltd",
"photoUrl": "https://example.com/photos/ada.jpg",
"companyLogoUrl": "https://example.com/brand/logo.png",
"bio": "Builds calculating machines and the teams around them.",
"emails": [
{
"label": "Work",
"value": "ada@example.com"
}
],
"phones": [
{
"label": "Mobile",
"value": "+44 20 7946 0958"
}
],
"links": [
{
"type": "website",
"url": "example.com"
},
{
"type": "linkedin",
"url": "linkedin.com/in/ada-lovelace"
}
],
"address": {
"city": "London",
"country": "United Kingdom"
}
},
"createdAt": "2026-10-09T09:30:00.000Z",
"updatedAt": "2026-10-09T09:30:00.000Z"
}Note two values: id, which every later step uses, and url, which is the page people will see. Check that paused is false.
Retrieve the card
Your application will not have the create response to hand next week. It will have the id it stored. Read the card back with it:
curl "https://api.qrbold.com/api/public/v1/cards/cmf3k2a9x0001" \
-H "Authorization: Bearer $QRBOLD_API_KEY"Replace cmf3k2a9x0001 with your card’s id. The response is the same card object.
Download the QR code
The card already has a QR code. Ask for its image using the card’s id. No separate QR code is created.
curl "https://api.qrbold.com/api/public/v1/qr-codes/cmf3k2a9x0001/image?format=png&size=1024" \
-H "Authorization: Bearer $QRBOLD_API_KEY" \
--output qr.pngThe response is the PNG itself. The samples save it as qr.png. For print, ask for format=svg or format=pdf instead; see image options.
Scan it
- Open the saved image on your computer screen.
- Point a phone camera at it and tap the link that appears.
- The card for Ada Lovelace opens, at the address in
url. - It opens without asking anyone to sign in.
- Tapping the save-contact button offers to add Ada to the phone’s contacts.
That scan has now been counted. GET /analytics/{card id} will show it; see analytics.
The complete script
Everything above in one file. Save it as create-card.mjs and run node create-card.mjs. It uses only what ships with Node.js, reads the key from the environment, sets a timeout, sends an idempotency key with the create request, and reports errors with the API’s own code and message.
// create-card.mjs
// Creates a digital business card, reads it back and saves its QR code.
// Run with: node create-card.mjs (Node.js 20 or later, no packages)
import { randomUUID } from 'node:crypto';
import { writeFile } from 'node:fs/promises';
const BASE_URL = 'https://api.qrbold.com/api/public/v1';
const API_KEY = process.env.QRBOLD_API_KEY;
if (!API_KEY) {
console.error('Set the QRBOLD_API_KEY environment variable first.');
process.exit(1);
}
// One helper for every request: adds the key, checks the status, and turns a
// failure into an Error that carries the API's own code and message.
async function qrbold(method, path, { body, idempotencyKey } = {}) {
const res = await fetch(BASE_URL + path, {
method,
headers: {
Authorization: `Bearer ${API_KEY}`,
...(body ? { 'Content-Type': 'application/json' } : {}),
...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
},
body: body ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) {
// Most errors are { error: { type, code, message, details } }. A 429 is not,
// so fall back to the status.
const problem = await res.json().catch(() => null);
const error = new Error(problem?.error?.message ?? problem?.message ?? res.statusText);
error.status = res.status;
error.code = problem?.error?.code ?? null;
error.details = problem?.error?.details ?? null;
throw error;
}
return res;
}
async function main() {
// 1. Check the key. GET /me needs no scope and changes nothing.
const me = await (await qrbold('GET', '/me')).json();
console.log(`Key "${me.key.name}" is valid for ${me.account.email}`);
// 2. Create the card. The Idempotency-Key makes a retry of this exact
// request return the same card instead of creating a second one.
const created = await (
await qrbold('POST', '/cards', {
idempotencyKey: randomUUID(),
body: {
fields: {
"firstName": "Ada",
"lastName": "Lovelace",
"title": "Head of Engineering",
"company": "Analytical Engines Ltd",
"photoUrl": "https://example.com/photos/ada.jpg",
"companyLogoUrl": "https://example.com/brand/logo.png",
"bio": "Builds calculating machines and the teams around them.",
"emails": [
{
"label": "Work",
"value": "ada@example.com"
}
],
"phones": [
{
"label": "Mobile",
"value": "+44 20 7946 0958"
}
],
"links": [
{
"type": "website",
"url": "example.com"
},
{
"type": "linkedin",
"url": "linkedin.com/in/ada-lovelace"
}
],
"address": {
"city": "London",
"country": "United Kingdom"
}
},
},
})
).json();
console.log('Created card', created.id);
console.log('Public URL ', created.url);
if (created.paused || created.status !== 'published') {
console.warn('The card exists but is not live. Check the plan in the dashboard.');
}
// 3. Read it back by id, the way your application will later.
const card = await (await qrbold('GET', `/cards/${created.id}`)).json();
console.log('Retrieved ', card.name);
// 4. Download the QR code. The card's own id is used: no separate QR code
// is created. The response is the image itself, not JSON.
const image = await qrbold('GET', `/qr-codes/${card.id}/image?format=png&size=1024`);
await writeFile('card-qr.png', Buffer.from(await image.arrayBuffer()));
console.log('Saved card-qr.png');
console.log(`\nScan card-qr.png with a phone. It should open ${card.url}`);
}
main().catch((error) => {
console.error(`Failed: ${error.status ?? ''} ${error.code ?? ''} ${error.message}`);
if (error.details) console.error(JSON.stringify(error.details, null, 2));
process.exit(1);
});
Key "HR onboarding sync" is valid for you@example.com
Created card cmf3k2a9x0001
Public URL https://qrbold.com/c/k3Vq8ZtB
Retrieved Ada Lovelace
Saved card-qr.png
Scan card-qr.png with a phone. It should open https://qrbold.com/c/k3Vq8ZtBRunning it a second time creates a second card, because each run generates a new idempotency key. That is correct: an idempotency key protects a retry of one request, not a repeat of the whole job. To make a job repeatable, store the ids you created, or use a campaign endpoint, which refuses a second card for the same person.
Common errors
| The script prints | Meaning | Fix |
|---|---|---|
Set the QRBOLD_API_KEY environment variable first. | The variable is not set in this terminal. | Set it in the same terminal window you run the script from. |
401 invalid_api_key | The key is wrong, revoked or expired. | Copy the whole key again, or create a new one. |
403 upgrade_required_api | The plan does not include the REST API. | Upgrade to Pro or the Business card plan. |
403 forbidden | The key lacks cards:write or cards:read. | Create a key with both scopes. |
403 limit_exhausted_cards | The plan has no card seats left. | Delete an unused card or add seats. |
422 validation_failed | A field value is not valid. | The script prints the details; each names a field. |
429 | Too many requests this minute. | Wait a minute and run it again. |
| A timeout or network error | The request did not complete. | Run it again. If the create step timed out, check the dashboard first so you do not create a duplicate. |
The full list is in the error reference.
Before you ship
The script is a starting point. Before it becomes part of a product:
- Store the id and the url of every card in your own database, next to the person they belong to.
- Reuse the idempotency key when you retry. Generate it once per person, keep it, and send the same one on every attempt for that person.
- Retry only what is worth retrying: network errors, 429 and 5xx. A 4xx will fail the same way again.
- Run it on a server. Never ship the key to a browser or a mobile app.
- Use a template so every card shares your design, and send
templateId.
Going to production covers each of these.
To remove the card this tutorial created:
curl -X DELETE "https://api.qrbold.com/api/public/v1/cards/cmf3k2a9x0001" \
-H "Authorization: Bearer $QRBOLD_API_KEY"